@jskit-ai/resource-crud-core 0.1.101 → 0.1.103
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/resource-crud-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.103",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
"./shared/crudResource": "./src/shared/crudResource.js"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@jskit-ai/kernel": "0.1.
|
|
17
|
-
"@jskit-ai/resource-core": "0.1.
|
|
16
|
+
"@jskit-ai/kernel": "0.1.161",
|
|
17
|
+
"@jskit-ai/resource-core": "0.1.103"
|
|
18
18
|
},
|
|
19
19
|
"description": "Shared CRUD resource, field, lookup, filter, and namespace contracts.",
|
|
20
20
|
"jskit": {
|
|
@@ -32,20 +32,6 @@
|
|
|
32
32
|
"client": {
|
|
33
33
|
"providers": []
|
|
34
34
|
}
|
|
35
|
-
},
|
|
36
|
-
"mutations": {
|
|
37
|
-
"dependencies": {
|
|
38
|
-
"runtime": {
|
|
39
|
-
"@jskit-ai/resource-crud-core": "0.1.101"
|
|
40
|
-
},
|
|
41
|
-
"dev": {}
|
|
42
|
-
},
|
|
43
|
-
"packageJson": {
|
|
44
|
-
"scripts": {}
|
|
45
|
-
},
|
|
46
|
-
"procfile": {},
|
|
47
|
-
"files": [],
|
|
48
|
-
"text": []
|
|
49
35
|
}
|
|
50
36
|
}
|
|
51
37
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: crud/resource-contract
|
|
3
|
+
title: Owner-scoped CRUD resource contract
|
|
4
|
+
summary: Define an authenticated application resource whose records belong to the current user.
|
|
5
|
+
keywords: authenticated, crud, database, owner-scoped, resource, user
|
|
6
|
+
requires: @jskit-ai/resource-crud-core
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Owner-scoped CRUD resource contract
|
|
10
|
+
|
|
11
|
+
## Use when
|
|
12
|
+
|
|
13
|
+
Use this pattern when an authenticated application stores records that belong
|
|
14
|
+
to one user and standard list, view, create, patch, and delete behaviour fits
|
|
15
|
+
the product.
|
|
16
|
+
|
|
17
|
+
The example is intentionally a normal source file. Copy it directly when the
|
|
18
|
+
shape fits, adapt its fields and language for another entity, or read it as a
|
|
19
|
+
reference for `defineCrudResource()`.
|
|
20
|
+
|
|
21
|
+
## Do not use when
|
|
22
|
+
|
|
23
|
+
Do not use this pattern for public records, workspace-owned records, inherited
|
|
24
|
+
ownership, composite identities, or a domain whose operations do not match
|
|
25
|
+
normal CRUD behaviour. Select a pattern matching the real ownership and
|
|
26
|
+
operation model instead of changing `autofilter` merely to make a test pass.
|
|
27
|
+
|
|
28
|
+
## Product decisions
|
|
29
|
+
|
|
30
|
+
Know these decisions before adapting the example:
|
|
31
|
+
|
|
32
|
+
- whether records truly belong to the current user
|
|
33
|
+
- which fields are required, optional, nullable, searchable, or hidden
|
|
34
|
+
- whether a value is free text or a constrained vocabulary
|
|
35
|
+
- which operations the product actually exposes
|
|
36
|
+
- the user-facing success and failure language
|
|
37
|
+
|
|
38
|
+
Those decisions come from the product conversation or existing application,
|
|
39
|
+
not from a JSKIT questionnaire.
|
|
40
|
+
|
|
41
|
+
## Invariants
|
|
42
|
+
|
|
43
|
+
- The persisted table has a normal single-column primary key.
|
|
44
|
+
- User ownership is represented by a non-null `user_id` column.
|
|
45
|
+
- The resource's hidden `userId` field maps to that ownership column.
|
|
46
|
+
- `autofilter: "user"` is used only for genuine current-user ownership.
|
|
47
|
+
- Output, create, and patch participation is explicit on each product field.
|
|
48
|
+
- Temporal database values use the declared storage serializer.
|
|
49
|
+
- The source-controlled migration and resource contract agree.
|
|
50
|
+
- Server policy tests prove that one user cannot read or mutate another user's
|
|
51
|
+
records.
|
|
52
|
+
|
|
53
|
+
## Framework APIs
|
|
54
|
+
|
|
55
|
+
The example uses `defineCrudResource()` from
|
|
56
|
+
`@jskit-ai/resource-crud-core/shared/crudResource`. That API derives the normal
|
|
57
|
+
CRUD operation validators from one readable field contract.
|
|
58
|
+
|
|
59
|
+
Higher layers should consume this resource through JSKIT's CRUD service,
|
|
60
|
+
repository, route, and UI APIs. They should not rebuild field validators or
|
|
61
|
+
serialize requests independently.
|
|
62
|
+
|
|
63
|
+
## Example files
|
|
64
|
+
|
|
65
|
+
- `example/bookResource.js` is a complete resource contract for personal book
|
|
66
|
+
records.
|
|
67
|
+
|
|
68
|
+
The example deliberately uses a concrete domain. Rename and edit normal source
|
|
69
|
+
rather than feeding it through placeholder interpolation.
|
|
70
|
+
|
|
71
|
+
## Variation points
|
|
72
|
+
|
|
73
|
+
Safe adaptations include:
|
|
74
|
+
|
|
75
|
+
- namespace and table name
|
|
76
|
+
- product fields and validation
|
|
77
|
+
- searchable fields and default sort
|
|
78
|
+
- enabled CRUD operations
|
|
79
|
+
- user-facing messages
|
|
80
|
+
- lookup relationships supported by the resource APIs
|
|
81
|
+
|
|
82
|
+
Changing ownership, access, identity, or persistence architecture selects a
|
|
83
|
+
different pattern and requires corresponding policy and migration evidence.
|
|
84
|
+
|
|
85
|
+
## Verification
|
|
86
|
+
|
|
87
|
+
- Import the resource and prove all intended operations exist.
|
|
88
|
+
- Validate representative accepted and rejected field values.
|
|
89
|
+
- Rebuild the table from source-controlled migrations in a disposable database.
|
|
90
|
+
- Run positive current-user CRUD tests.
|
|
91
|
+
- Run negative cross-user read and mutation tests.
|
|
92
|
+
- Run the application's focused tests and broad verifier at sign-off.
|
|
93
|
+
|
|
94
|
+
## Avoid
|
|
95
|
+
|
|
96
|
+
- generator provenance or scaffold-shape metadata
|
|
97
|
+
- a field-by-field questionnaire
|
|
98
|
+
- a live table as the only source of schema truth
|
|
99
|
+
- app-specific request serializers that duplicate the resource contract
|
|
100
|
+
- hidden ownership aliases other than the framework's explicit ownership field
|
|
101
|
+
- durable receipts recording that this pattern was copied
|
|
102
|
+
- changing an installed baseline migration instead of adding a new migration
|
|
103
|
+
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { defineCrudResource } from "@jskit-ai/resource-crud-core/shared/crudResource";
|
|
2
|
+
|
|
3
|
+
const bookResource = defineCrudResource({
|
|
4
|
+
namespace: "books",
|
|
5
|
+
tableName: "books",
|
|
6
|
+
apiAccess: "authenticated",
|
|
7
|
+
schema: {
|
|
8
|
+
userId: {
|
|
9
|
+
type: "id",
|
|
10
|
+
required: true,
|
|
11
|
+
hidden: true,
|
|
12
|
+
operations: {}
|
|
13
|
+
},
|
|
14
|
+
title: {
|
|
15
|
+
type: "string",
|
|
16
|
+
maxLength: 255,
|
|
17
|
+
required: true,
|
|
18
|
+
search: true,
|
|
19
|
+
operations: {
|
|
20
|
+
output: { required: true },
|
|
21
|
+
create: { required: true },
|
|
22
|
+
patch: { required: false }
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
author: {
|
|
26
|
+
type: "string",
|
|
27
|
+
maxLength: 255,
|
|
28
|
+
required: true,
|
|
29
|
+
search: true,
|
|
30
|
+
operations: {
|
|
31
|
+
output: { required: true },
|
|
32
|
+
create: { required: true },
|
|
33
|
+
patch: { required: false }
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
notes: {
|
|
37
|
+
type: "string",
|
|
38
|
+
maxLength: 65535,
|
|
39
|
+
nullable: true,
|
|
40
|
+
search: true,
|
|
41
|
+
operations: {
|
|
42
|
+
output: { required: true },
|
|
43
|
+
create: { required: false },
|
|
44
|
+
patch: { required: false }
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
createdAt: {
|
|
48
|
+
type: "dateTime",
|
|
49
|
+
temporalPrecision: 0,
|
|
50
|
+
default: "now()",
|
|
51
|
+
storage: { writeSerializer: "datetime-utc" },
|
|
52
|
+
operations: {
|
|
53
|
+
output: { required: true }
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
updatedAt: {
|
|
57
|
+
type: "dateTime",
|
|
58
|
+
temporalPrecision: 0,
|
|
59
|
+
default: "now()",
|
|
60
|
+
storage: { writeSerializer: "datetime-utc" },
|
|
61
|
+
operations: {
|
|
62
|
+
output: { required: true }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
searchSchema: {
|
|
67
|
+
id: { type: "id", actualField: "id" },
|
|
68
|
+
q: {
|
|
69
|
+
type: "string",
|
|
70
|
+
oneOf: ["title", "author", "notes"],
|
|
71
|
+
filterOperator: "like",
|
|
72
|
+
splitBy: " ",
|
|
73
|
+
matchAll: true
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
defaultSort: ["-createdAt"],
|
|
77
|
+
autofilter: "user",
|
|
78
|
+
messages: {
|
|
79
|
+
validation: "Fix invalid values and try again.",
|
|
80
|
+
saveSuccess: "Book saved.",
|
|
81
|
+
saveError: "Unable to save this book.",
|
|
82
|
+
deleteSuccess: "Book deleted.",
|
|
83
|
+
deleteError: "Unable to delete this book."
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
export { bookResource };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { bookResource } from "../patterns/resource-contract/example/bookResource.js";
|
|
4
|
+
|
|
5
|
+
test("owner-scoped resource pattern is executable framework source", () => {
|
|
6
|
+
assert.equal(bookResource.namespace, "books");
|
|
7
|
+
assert.equal(bookResource.tableName, "books");
|
|
8
|
+
assert.equal(bookResource.apiAccess, "authenticated");
|
|
9
|
+
assert.equal(bookResource.autofilter, "user");
|
|
10
|
+
assert.deepEqual(
|
|
11
|
+
Object.keys(bookResource.operations),
|
|
12
|
+
["list", "view", "create", "patch", "delete"]
|
|
13
|
+
);
|
|
14
|
+
assert.deepEqual(
|
|
15
|
+
bookResource.operations.create.body.schema.create({
|
|
16
|
+
title: "The Dispossessed",
|
|
17
|
+
author: "Ursula K. Le Guin",
|
|
18
|
+
notes: null
|
|
19
|
+
}).errors,
|
|
20
|
+
{}
|
|
21
|
+
);
|
|
22
|
+
assert.match(
|
|
23
|
+
bookResource.operations.create.body.schema.create({
|
|
24
|
+
author: "Ursula K. Le Guin"
|
|
25
|
+
}).errors.title.message,
|
|
26
|
+
/required/i
|
|
27
|
+
);
|
|
28
|
+
});
|