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/tidy-blocks-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@geoprotocol/geo-sdk": minor
---

Add `Ops.textBlocks.create` and `Ops.dataBlocks.create`, returning `{ id, ops }` and accepting an optional stable block ID. The existing `TextBlock.make` and `DataBlock.make` helpers remain compatible with their `Op[]` return type and are now deprecated in favor of the new builders.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,25 @@ const { ops } = Ops.relations.create({
});
```

### Blocks

Create text and data blocks with generated or stable IDs:

```ts
import { Ops } from "@geoprotocol/geo-sdk";

const { id: textBlockId, ops: textBlockOps } = Ops.textBlocks.create({
fromId: pageId,
text: "# Heading",
});

const { id: dataBlockId, ops: dataBlockOps } = Ops.dataBlocks.create({
fromId: pageId,
sourceType: "QUERY",
id: stableDataBlockId,
});
```

### Images

Image creation goes through `geo.images.create(...)`. The configured client uploads the image, detects dimensions when possible, and returns the image entity ops.
Expand Down Expand Up @@ -1110,6 +1129,8 @@ The legacy namespaces remain exported for compatibility, but new code should pre
| `Graph.deleteEntity(...)` | `geo.entities.delete(...)` |
| `Graph.createImage(...)` | `geo.images.create(...)` |
| `Graph.createComment(...)` | `geo.comments.create(...)` or `Ops.comments.create(...)` with supplied reply context |
| `TextBlock.make(...)` | `Ops.textBlocks.create(...)` |
| `DataBlock.make(...)` | `Ops.dataBlocks.create(...)` |
| `Ipfs.publishEdit(...)` | `geo.personalSpaces.publishEdit(...)` or `geo.daoSpaces.proposeEdit(...)` |
| `Ipfs.uploadImage(...)` | `geo.storage.uploadImage(...)` |
| `Ipfs.uploadCSV(...)` | `geo.storage.uploadCSV(...)` |
Expand Down
2 changes: 2 additions & 0 deletions src/blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* in TypeScript.
*
* @since 0.0.6
* @deprecated Use `Ops.dataBlocks.create(...)`.
*/
export * as DataBlock from './core/blocks/data.js';

Expand All @@ -11,5 +12,6 @@ export * as DataBlock from './core/blocks/data.js';
* in TypeScript.
*
* @since 0.0.6
* @deprecated Use `Ops.textBlocks.create(...)`.
*/
export * as TextBlock from './core/blocks/text.js';
29 changes: 29 additions & 0 deletions src/core/blocks/data.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ import {
} from '../ids/system.js';
import { make } from './data.js';

it('preserves the legacy array return contract', () => {
const ops = make({
fromId: Id('5871e8f7b71948979c4dcf7c518d32ef'),
sourceType: 'QUERY',
});

expect(Array.isArray(ops)).toBe(true);
});

it('should generate ops for a data block entity', () => {
const fromId = Id('5871e8f7b71948979c4dcf7c518d32ef');
const ops = make({
Expand Down Expand Up @@ -92,6 +101,26 @@ it('should generate ops for a data block entity with a name', () => {
}
});

it('should use a provided block id for deterministic re-runs', () => {
const fromId = Id('5871e8f7b71948979c4dcf7c518d32ef');
const blockId = Id('a1b2c3d4e5f64789a0b1c2d3e4f50617');
const ops = make({
fromId,
sourceType: 'QUERY',
id: blockId,
});

const typeRelOp = ops[0] as CreateRelation;
expect(typeRelOp.from).toEqual(toGrcId(blockId));
const blocksRelOp = ops[2] as CreateRelation;
expect(blocksRelOp.to).toEqual(toGrcId(blockId));
});

it('should throw on an invalid provided id', () => {
const fromId = Id('5871e8f7b71948979c4dcf7c518d32ef');
expect(() => make({ fromId, sourceType: 'QUERY', id: 'not-a-valid-id' })).toThrow();
});

it('should generate ops for a COLLECTION data source type', () => {
const fromId = Id('5871e8f7b71948979c4dcf7c518d32ef');
const ops = make({
Expand Down
88 changes: 11 additions & 77 deletions src/core/blocks/data.ts
Original file line number Diff line number Diff line change
@@ -1,94 +1,28 @@
/**
* This module provides utility functions for working with data blocks
* in TypeScript.
* This module provides legacy utility functions for working with data blocks.
*
* @since 0.0.6
*/

import type { Op } from '@geoprotocol/grc-20';
import { createRelation } from '../../graph/create-relation.js';
import { updateEntity } from '../../graph/update-entity.js';
import { Id } from '../../id.js';
import { generate } from '../../id-utils.js';
import { SystemIds } from '../../system-ids.js';
import { BLOCKS, DATA_BLOCK, DATA_SOURCE_TYPE_RELATION_TYPE, NAME_PROPERTY, TYPES_PROPERTY } from '../ids/system.js';
import { create, type DataBlockParams } from '../../ops/data-blocks.js';

type DataBlockSourceType = 'QUERY' | 'COLLECTION' | 'GEO';

function getSourceTypeId(sourceType: DataBlockSourceType) {
switch (sourceType) {
case 'COLLECTION':
return SystemIds.COLLECTION_DATA_SOURCE;
case 'GEO':
return SystemIds.ALL_OF_GEO_DATA_SOURCE;
case 'QUERY':
return SystemIds.QUERY_DATA_SOURCE;
}
}

type DataBlockParams = {
fromId: string;
sourceType: DataBlockSourceType;
position?: string;
name?: string;
};
export type { DataBlockParams, DataBlockSourceType } from '../../ops/data-blocks.js';

/**
* Returns the ops to create an entity representing a Data Block.
* Returns the ops to create an entity representing a data block.
*
* @deprecated Use `Ops.dataBlocks.create(...)` to receive both the block ID and ops.
*
* @example
* ```ts
* const ops = DataBlock.make({
* fromId: 'from-id',
* sourceType: 'COLLECTION',
* // optional
* position: 'position-string',
* name: 'name',
* fromId: pageId,
* sourceType: 'QUERY',
* id: blockId,
* });
* ```
*
* @param param args {@link TextBlockParams}
* @returns ops – The ops for the Data Block entity: {@link Op}[]
*/
export function make({ fromId, sourceType, position, name }: DataBlockParams): Op[] {
const newBlockId = generate();

const ops: Op[] = [];
const { ops: dataBlockTypeOps } = createRelation({
fromEntity: newBlockId,
type: TYPES_PROPERTY,
toEntity: DATA_BLOCK,
});
ops.push(...dataBlockTypeOps);

const { ops: dataBlockSourceTypeOps } = createRelation({
fromEntity: newBlockId,
type: DATA_SOURCE_TYPE_RELATION_TYPE,
toEntity: getSourceTypeId(sourceType),
});
ops.push(...dataBlockSourceTypeOps);

const { ops: dataBlockRelationOps } = createRelation({
fromEntity: Id(fromId),
type: BLOCKS,
toEntity: Id(newBlockId),
position,
});
ops.push(...dataBlockRelationOps);

if (name) {
const { ops: nameOps } = updateEntity({
id: newBlockId,
values: [
{
property: NAME_PROPERTY,
type: 'text',
value: name,
},
],
});
ops.push(...nameOps);
}

return ops;
export function make(params: DataBlockParams): Op[] {
return create(params).ops;
}
39 changes: 39 additions & 0 deletions src/core/blocks/text.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ import { toGrcId } from '../../id-utils.js';
import { BLOCKS, MARKDOWN_CONTENT, TEXT_BLOCK, TYPES_PROPERTY } from '../ids/system.js';
import { make } from './text.js';

it('preserves the legacy array return contract', () => {
const ops = make({
fromId: Id('5871e8f7b71948979c4dcf7c518d32ef'),
text: 'test-text',
});

expect(Array.isArray(ops)).toBe(true);
});

it('should generate ops for a text block entity', () => {
const fromId = Id('5871e8f7b71948979c4dcf7c518d32ef');
const ops = make({
Expand Down Expand Up @@ -61,6 +70,36 @@ it('should generate ops for a text block without position', () => {
expect(blocksRelOp.position).toBeUndefined();
});

it('should use a provided block id for deterministic re-runs', () => {
const fromId = Id('5871e8f7b71948979c4dcf7c518d32ef');
const blockId = Id('a1b2c3d4e5f64789a0b1c2d3e4f50617');
const ops = make({
fromId,
text: 'test-text',
id: blockId,
});

const typeRelOp = ops[0] as CreateRelation;
expect(typeRelOp.from).toEqual(toGrcId(blockId));
const blocksRelOp = ops[2] as CreateRelation;
expect(blocksRelOp.to).toEqual(toGrcId(blockId));
});

it('should throw on an invalid provided id', () => {
const fromId = Id('5871e8f7b71948979c4dcf7c518d32ef');
expect(() => make({ fromId, text: 'test-text', id: 'not-a-valid-id' })).toThrow();
});

it('should encode a dashed provided id to the same bytes as its dashless form', () => {
const fromId = Id('5871e8f7b71948979c4dcf7c518d32ef');
const dashed = make({ fromId, text: 't', id: 'a1b2c3d4-e5f6-4789-a0b1-c2d3e4f50617' });
const dashless = make({ fromId, text: 't', id: 'a1b2c3d4e5f64789a0b1c2d3e4f50617' });

const dashedTypeRel = dashed[0] as CreateRelation;
const dashlessTypeRel = dashless[0] as CreateRelation;
expect(dashedTypeRel.from).toEqual(dashlessTypeRel.from);
});

it('should handle empty text', () => {
const fromId = Id('5871e8f7b71948979c4dcf7c518d32ef');
const ops = make({
Expand Down
60 changes: 11 additions & 49 deletions src/core/blocks/text.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,28 @@
/**
* This module provides utility functions for working with text blocks
* in TypeScript.
* This module provides legacy utility functions for working with text blocks.
*
* @since 0.0.6
*/

import type { Op } from '@geoprotocol/grc-20';
import { createRelation } from '../../graph/create-relation.js';
import { updateEntity } from '../../graph/update-entity.js';
import { Id } from '../../id.js';
import { generate } from '../../id-utils.js';
import { BLOCKS, MARKDOWN_CONTENT, TEXT_BLOCK, TYPES_PROPERTY } from '../ids/system.js';
import { create, type TextBlockParams } from '../../ops/text-blocks.js';

type TextBlockParams = { fromId: string; text: string; position?: string };
export type { TextBlockParams } from '../../ops/text-blocks.js';

/**
* Returns the ops to create an entity representing a Text Block.
* Returns the ops to create an entity representing a text block.
*
* @deprecated Use `Ops.textBlocks.create(...)` to receive both the block ID and ops.
*
* @example
* ```ts
* const ops = TextBlock.make({
* fromId: 'from-id',
* text: 'text',
* // optional
* position: 'position-string',
* fromId: pageId,
* text: '# Heading',
* id: blockId,
* });
* ```
*
* @param param args {@link TextBlockParams}
* @returns ops – The ops for the Text Block entity: {@link Op}[]
*/
export function make({ fromId, text, position }: TextBlockParams): Op[] {
const newBlockId = generate();

const ops: Op[] = [];

const { ops: textBlockTypeOps } = createRelation({
fromEntity: newBlockId,
type: TYPES_PROPERTY,
toEntity: TEXT_BLOCK,
});
ops.push(...textBlockTypeOps);

const { ops: textBlockMarkdownTextOps } = updateEntity({
id: newBlockId,
values: [
{
property: MARKDOWN_CONTENT,
type: 'text',
value: text,
},
],
});
ops.push(...textBlockMarkdownTextOps);

const { ops: textBlockRelationOps } = createRelation({
fromEntity: Id(fromId),
type: BLOCKS,
toEntity: newBlockId,
position,
});
ops.push(...textBlockRelationOps);

return ops;
export function make(params: TextBlockParams): Op[] {
return create(params).ops;
}
Loading
Loading