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
10 changes: 0 additions & 10 deletions .changeset/wicked-lions-wonder.md

This file was deleted.

6 changes: 4 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ ls docs/ # list all docs
head -4 docs/*.md # get summaries (or replace * with a specific file)
```

Each package also has a `README.md` with API docs and a `## Changelog` at the bottom.

## Planning and implementation workflow

### When to plan
Expand All @@ -31,7 +33,7 @@ Any task that changes package code requires a plan. Simple checks, info gatherin
### Creating a plan

1. **Inspect the relevant code thoroughly** before writing anything. Read the source files, tests, and existing docs that relate to the task.
2. Create a new doc in `docs/` with the next 3-digit prefix (e.g. `005-add-filter-support.md`) **for new feature/PR scope**. For follow-ups in the same thread/PR scope, update the existing plan doc. Start with YAML frontmatter.
2. Create a new doc in `docs/` with the next 3-digit prefix (e.g. `005-add-filter-support.md`). Start with YAML frontmatter.
3. Write the plan with these sections:
- **Key considerations and choices** — tradeoffs, open questions, alternatives
- **Potential problems** — what could go wrong, edge cases
Expand All @@ -54,4 +56,4 @@ Any task that changes package code requires a plan. Simple checks, info gatherin
Before committing final changes or preparing a PR:

1. **Consolidate the plan doc** — collapse alternatives into the choices that were made, summarize implementation details and breaking changes, keep a brief problems section if relevant, remove anything redundant for future readers.
2. **Run changesets** Ask the user if this is a patch/minor/major change, then run `npx changesets/cli` and provide an appropriate changelog message covering behavior changes, new APIs, breaking changes, and migration steps.
2. **Update `## Changelog`** in each affected package's `README.md` — user-facing entry covering behavior changes, new APIs, breaking changes, and migration steps.
185 changes: 39 additions & 146 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,35 +85,41 @@ export class Person extends Shape {
## Queries: Create, Select, Update, Delete

Queries are expressed with the same Shape classes and compile to a query object that a store executes.
Use this section as a quick start. Detailed query variations are documented in `Query examples` below.

A few quick examples:

**1) Select one field for all matching nodes**
```typescript
/* Result: Array<{id: string; name: string}> */
const names = await Person.select((p) => p.name);
/* names: {id: string; name: string}[] */
```

**2) Select all decorated fields of nested related nodes**
```typescript
const allFriends = await Person.select((p) => p.knows.selectAll());
/* allFriends: {
id?: string;
knows: {
id?: string;
...all decorated Person fields...
}[]
}[] */
```

**3) Apply a simple mutation**
```typescript
const myNode = {id: 'https://my.app/node1'};
/* Result: {id: string; name: string} | null */
const person = await Person.select(myNode, (p) => p.name);
const missing = await Person.select({id: 'https://my.app/missing'}, (p) => p.name); // null

/* Result: {id: string} & UpdatePartial<Person> */
const created = await Person.create({
name: 'Alice',
knows: [{id: 'https://my.app/node2'}],
});

const updated = await Person.update(myNode, {
name: 'Alicia',
});
/* updated: {id: string} & UpdatePartial<Person> */

// Overwrite a multi-value property
const overwriteFriends = await Person.update(myNode, {
knows: [{id: 'https://my.app/node2'}],
});

// Add/remove items in a multi-value property
const addRemoveFriends = await Person.update(myNode, {
knows: {
add: [{id: 'https://my.app/node3'}],
remove: [{id: 'https://my.app/node2'}],
},
});

/* Result: {deleted: Array<{id: string}>, count: number} */
await Person.delete(myNode);
```

## Storage configuration
Expand Down Expand Up @@ -166,13 +172,10 @@ Result types are inferred from your Shape definitions and the selected paths. Ex

#### Basic selection
```typescript
/* names: {id: string; name: string}[] */
/* Result: Array<{id: string; name: string}> */
const names = await Person.select((p) => p.name);

/* friends: {
id: string;
knows: { id: string }[]
}[] */
/* Result: Array<{id: string; knows: Array<{id: string}>}> */
const friends = await Person.select((p) => p.knows);

const dates = await Person.select((p) => [p.birthDate, p.name]);
Expand All @@ -199,12 +202,6 @@ const deep = await Person.select((p) => p.knows.bestFriend.name);
const detailed = await Person.select((p) =>
p.knows.select((f) => f.name),
);

const allPeople = await Person.selectAll();

const detailedAll = await Person.select((p) =>
p.knows.selectAll(),
);
```

#### Where + equals
Expand Down Expand Up @@ -264,9 +261,7 @@ const custom = await Person.select((p) => ({
}));
```

#### Query As (type casting to a sub shape)
If person.pets returns an array of Pets. And Dog extends Pet.
And you want to select properties of those pets that are dogs:
#### Query As (type casting)
```typescript
const guards = await Person.select((p) => p.pets.as(Dog).guardDogLevel);
```
Expand Down Expand Up @@ -298,131 +293,29 @@ const preloaded = await Person.select((p) => [
]);
```

#### Create

#### Create / Update / Delete
```typescript
/* Result: {id: string} & UpdatePartial<Person> */
const created = await Person.create({name: 'Alice'});
```
Where UpdatePartial<Shape> reflects the created properties.

#### Update

Update will patch any property that you send as payload and leave the rest untouched.
```typescript
/* Result: {id: string} & UpdatePartial<Person> */
const updated = await Person.update({id: 'https://my.app/node1'}, {name: 'Alicia'});
```
Returns:
```json
{
id:"https://my.app/node1",
name:"Alicia"
}
```

**Updating multi-value properties**
When updating a property that holds multiple values (one that returns an array in the results), you can either overwrite all the values with a new explicit array of values, or delete from/add to the current values.

To overwrite all values:
```typescript
// Overwrite the full set of "knows" values.
const overwriteFriends = await Person.update({id: 'https://my.app/person1'}, {
knows: [{id: 'https://my.app/person2'}],
// Overwrite a multi-value property
const overwriteFriends = await Person.update({id: 'https://my.app/node1'}, {
knows: [{id: 'https://my.app/node2'}],
});
```
The result will contain an object with `updatedTo`, to indicate that previous values were overwritten to this new set of values:
```json
{
id: "https://my.app/person1",
knows: {
updatedTo: [{id:"https://my.app/person2"}],
}
}
```

To make incremental changes to the current set of values you can provide an object with `add` and/or `remove` keys:
```typescript
// Add one value and remove one value without replacing the whole set.
const addRemoveFriends = await Person.update({id: 'https://my.app/person1'}, {
// Add/remove items in a multi-value property
const addRemoveFriends = await Person.update({id: 'https://my.app/node1'}, {
knows: {
add: [{id: 'https://my.app/person2'}],
remove: [{id: 'https://my.app/person3'}],
add: [{id: 'https://my.app/node3'}],
remove: [{id: 'https://my.app/node2'}],
},
});
```
This returns an object with the added and removed items
```json
{
id: "https://my.app/person1",
knows: {
added?: [{id:"https://my.app/person2"},
removed?: [{id:"https://my.app/person3"}],
}
}
```


#### Delete
To delete a node entirely:

```typescript
/* Result: {deleted: Array<{id: string}>, count: number} */
const deleted = await Person.delete({id: 'https://my.app/node1'});
```
Returns
```json
{
deleted:[
{id:"https://my.app/node1"}
],
count:1
}
```

To delete multiple nodes pass an array:

```typescript
/* Result: {deleted: Array<{id: string}>, count: number} */
const deleted = await Person.delete([{id: 'https://my.app/node1'},{id: 'https://my.app/node2'}]);
```


## Extending shapes

Shape classes can extend other shape classes. Subclasses inherit property shapes from their superclasses and may override them.
This example assumes `Person` from the `Shapes` section above.

```typescript
import {literalProperty} from '@_linked/core/shapes/SHACL';
import {createNameSpace} from '@_linked/core/utils/NameSpace';
import {linkedShape} from './package';

const schema = createNameSpace('https://schema.org/');
const EmployeeClass = schema('Employee');
const name = schema('name');
const employeeId = schema('employeeId');

@linkedShape
export class Employee extends Person {
static targetClass = EmployeeClass;

// Override inherited "name" with stricter constraints (still maxCount: 1)
@literalProperty({path: name, required: true, minLength: 2, maxCount: 1})
declare name: string;

@literalProperty({path: employeeId, required: true, maxCount: 1})
declare employeeId: string;
}
await Person.delete({id: 'https://my.app/node1'});
```

Override behavior:

- `NodeShape.getUniquePropertyShapes()` returns one property shape per label, with subclass overrides taking precedence.
- Overrides must be tighten-only for `minCount`, `maxCount`, and `nodeKind` (widening is rejected at registration time).
- If an override omits `minCount`, `maxCount`, or `nodeKind`, inherited values are kept.
- Current scope: compatibility checks for `datatype`, `class`, and `pattern` are not enforced yet.

## TODO

- Allow `preloadFor` to accept another query (not just a component).
Expand Down
72 changes: 0 additions & 72 deletions docs/002-select-all-property-shapes.md

This file was deleted.

Loading