Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/shaggy-windows-camp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@graphprotocol/grc-20": minor
---

allow to provide an existing id when creating an entity, image, type or property
11 changes: 8 additions & 3 deletions src/core/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,28 @@ type MakeImageParams = {
width: number;
height: number;
};
id?: Id;
};

/**
* Creates an entity representing an Image.
*
* @example
* ```ts
* const { id, ops } = Image.make({ cid: 'https://example.com/image.png', dimensions: { width: 100, height: 100 } });
* const { id, ops } = Image.make({
* cid: 'https://example.com/image.png',
* dimensions: { width: 100, height: 100 },
* id: imageId, // optional and will be generated if not provided
* });
* console.log(id); // 'gw9uTVTnJdhtczyuzBkL3X'
* console.log(ops); // [...]
* ```
*
* @returns id – base58 encoded v4 uuid representing the image entity: {@link MakeImageReturnType}
* @returns ops – The ops for the Image entity: {@link MakeImageReturnType}
*/
export function make({ cid, dimensions }: MakeImageParams): MakeImageReturnType {
const entityId = generate();
export function make({ cid, dimensions, id }: MakeImageParams): MakeImageReturnType {
const entityId = id ?? generate();
const ops: Array<Op> = [
Relation.make({
fromId: entityId,
Expand Down
24 changes: 24 additions & 0 deletions src/graph/create-entity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,4 +310,28 @@ describe('createEntity', () => {
expect(typeof entity.id).toBe('string');
expect(entity.ops).toHaveLength(9);
});

it('creates an entity with a provided id', async () => {
const entity = createEntity({
id: Id('WeUPYRkhnQLmHPH4S1ioc4'),
name: 'Yummy Coffee',
});

expect(entity).toBeDefined();
expect(entity.id).toBe('WeUPYRkhnQLmHPH4S1ioc4');
expect(entity.ops).toHaveLength(1);
expect(entity.ops[0]).toMatchObject({
type: 'SET_TRIPLE',
triple: {
attribute: NAME_PROPERTY,
entity: entity.id,
value: { type: 'TEXT', value: 'Yummy Coffee' },
},
});
});

it('throws an error if the provided id is invalid', () => {
// @ts-expect-error - invalid id type
expect(() => createEntity({ id: 'invalid' })).toThrow('Invalid id: "invalid" for `id` in `createEntity`');
});
});
8 changes: 6 additions & 2 deletions src/graph/create-entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type CreateEntityParams = DefaultProperties & {
* description: 'description of the entity',
* cover: imageEntityId,
* types: [typeEntityId1, typeEntityId2],
* id: entityId, // optional and will be generated if not provided
* properties: {
* // value property like text, number, url, time, point, checkbox
* [propertyId]: {
Expand Down Expand Up @@ -49,8 +50,11 @@ type CreateEntityParams = DefaultProperties & {
* @returns – {@link CreateResult}
* @throws Will throw an error if any provided ID is invalid
*/
export const createEntity = ({ name, description, cover, properties, types }: CreateEntityParams): CreateResult => {
const id = generate();
export const createEntity = ({ id: providedId, name, description, cover, properties, types }: CreateEntityParams): CreateResult => {
if (providedId) {
assertValid(providedId, '`id` in `createEntity`');
}
const id = providedId ?? generate();
const ops: Array<Op> = [];

ops.push(...createDefaultProperties({ entityId: id, name, description, cover }));
Expand Down
16 changes: 16 additions & 0 deletions src/graph/create-image.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { Id } from '../id.js';
import { SystemIds } from '../system-ids.js';
import { createImage } from './create-image.js';

Expand Down Expand Up @@ -129,6 +130,21 @@ describe('createImage', () => {
}
});

it('creates an image with a provided id', async () => {
const image = await createImage({
url: 'http://localhost:3000/image',
id: Id('WeUPYRkhnQLmHPH4S1ioc4'),
});

expect(image).toBeDefined();
expect(image.id).toBe('WeUPYRkhnQLmHPH4S1ioc4');
});

it('throws an error if the provided id is invalid', () => {
// @ts-expect-error - invalid id type
expect(async () => await createImage({ id: 'invalid', url: 'http://localhost:3000/image' })).rejects.toThrow('Invalid id: "invalid" for `id` in `createImage`');
});

it('throws an error if the image cannot be uploaded to IPFS', async () => {
vi.spyOn(global, 'fetch').mockImplementation(url => {
if (url.toString() === 'http://localhost:3000/image') {
Expand Down
12 changes: 10 additions & 2 deletions src/graph/create-image.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { type Id, assertValid } from '../id.js';
import { Image } from '../image.js';
import { uploadImage } from '../ipfs.js';
import type { CreateResult, Op } from '../types.js';
Expand All @@ -7,11 +8,13 @@ type CreateImageParams =
blob: Blob;
name?: string;
description?: string;
id?: Id;
}
| {
url: string;
name?: string;
description?: string;
id?: Id;
};

/**
Expand All @@ -25,22 +28,27 @@ type CreateImageParams =
* url: 'https://example.com/image.png',
* name: 'name of the image', // optional
* description: 'description of the image', // optional
* id: imageId, // optional and will be generated if not provided
* });
*
* const { id, ops } = createImage({
* blob: new Blob(…),
* name: 'name of the image', // optional
* description: 'description of the image', // optional
* id: imageId, // optional and will be generated if not provided
* });
* ```
* @param params – {@link CreateImageParams}
* @returns – {@link CreateResult}
* @throws Will throw an IpfsUploadError if the image cannot be uploaded to IPFS
*/
export const createImage = async ({ name, description, ...params }: CreateImageParams): Promise<CreateResult> => {
export const createImage = async ({ name, description, id: providedId, ...params }: CreateImageParams): Promise<CreateResult> => {
if (providedId) {
assertValid(providedId, '`id` in `createImage`');
}
const ops: Array<Op> = [];
const { cid, dimensions } = await uploadImage(params);
const { id, ops: imageOps } = Image.make({ cid, dimensions });
const { id, ops: imageOps } = Image.make({ cid, dimensions, id: providedId });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question

ops.push(...imageOps);
ops.push(...createDefaultProperties({ entityId: id, name, description }));

Expand Down
17 changes: 17 additions & 0 deletions src/graph/create-property.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, it } from 'vitest';
import { JOB_TYPE, ROLES_PROPERTY } from '../core/ids/content.js';
import { Id } from '../id.js';
import { createProperty } from './create-property.js';

describe('createProperty', () => {
Expand Down Expand Up @@ -73,4 +74,20 @@ describe('createProperty', () => {
expect(property.ops[4]?.type).toBe('CREATE_RELATION');
expect(property.ops[5]?.type).toBe('CREATE_RELATION');
});

it('creates a property with a provided id', async () => {
const property = createProperty({
id: Id('WeUPYRkhnQLmHPH4S1ioc4'),
name: 'Price',
type: 'NUMBER',
});

expect(property).toBeDefined();
expect(property.id).toBe('WeUPYRkhnQLmHPH4S1ioc4');
});

it('throws an error if the provided id is invalid', async () => {
// @ts-expect-error - invalid id type
expect(() => createProperty({ id: 'invalid' })).toThrow('Invalid id: "invalid" for `id` in `createProperty`');
});
});
8 changes: 6 additions & 2 deletions src/graph/create-property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,19 @@ type CreatePropertyParams = DefaultProperties &
* type: 'TEXT'
* description: 'description of the property',
* cover: imageEntityId,
* id: propertyId, // optional and will be generated if not provided
* });
* ```
* @param params – {@link CreatePropertyParams}
* @returns – {@link CreateResult}
* @throws Will throw an error if any provided ID is invalid
*/
export const createProperty = (params: CreatePropertyParams): CreateResult => {
const { name, description, cover } = params;
const entityId = generate();
const { id, name, description, cover } = params;
if (id) {
assertValid(id, '`id` in `createProperty`');
}
const entityId = id ?? generate();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question

const ops: Op[] = [];

ops.push(...createDefaultProperties({ entityId, name, description, cover }));
Expand Down
16 changes: 16 additions & 0 deletions src/graph/create-type.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest';
import { AUTHORS_PROPERTY, WEBSITE_PROPERTY } from '../core/ids/content.js';
import { NAME_PROPERTY, PROPERTY, SCHEMA_TYPE, TYPES_PROPERTY } from '../core/ids/system.js';
import { Id } from '../id.js';
import { createType } from './create-type.js';

describe('createType', () => {
Expand Down Expand Up @@ -100,4 +101,19 @@ describe('createType', () => {
type: 'CREATE_RELATION',
});
});

it('creates a type with a provided id', async () => {
const type = createType({
id: Id('WeUPYRkhnQLmHPH4S1ioc4'),
name: 'Article',
});

expect(type).toBeDefined();
expect(type.id).toBe('WeUPYRkhnQLmHPH4S1ioc4');
});

it('throws an error if the provided id is invalid', () => {
// @ts-expect-error - invalid id type
expect(() => createType({ id: 'invalid' })).toThrow('Invalid id: "invalid" for `id` in `createType`');
});
});
8 changes: 6 additions & 2 deletions src/graph/create-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,19 @@ type CreateTypeParams = DefaultProperties & {
* name: 'name of the type',
* description: 'description of the type',
* cover: imageEntityId,
* id: typeId, // optional and will be generated if not provided
* properties: [propertyEntityId1, propertyEntityId2],
* });
* ```
* @param params – {@link CreateTypeParams}
* @returns – {@link CreateResult}
* @throws Will throw an error if any provided ID is invalid
*/
export const createType = ({ name, description, cover, properties }: CreateTypeParams): CreateResult => {
const id = generate();
export const createType = ({ id: providedId, name, description, cover, properties }: CreateTypeParams): CreateResult => {
if (providedId) {
assertValid(providedId, '`id` in `createType`');
}
const id = providedId ?? generate();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aaand same question

const ops: Op[] = [];

ops.push(...createDefaultProperties({ entityId: id, name, description, cover }));
Expand Down
4 changes: 2 additions & 2 deletions src/id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ export function isValid(id: string): boolean {
}
}

export function assertValid(id: string) {
export function assertValid(id: string, sourceHint?: string) {
if (!isValid(id)) {
throw new Error(`Invalid id: ${id}`);
throw new Error(`Invalid id: "${id}"${sourceHint ? ` for ${sourceHint}` : ''}`);
}
}
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ export type ProposalStatus = 'PROPOSED' | 'ACCEPTED' | 'REJECTED' | 'CANCELED' |
export type GraphUri = `graph://${string}`;

export type DefaultProperties = {
id?: Id;
name?: string;
description?: string;
cover?: Id;
Expand Down
Loading