@sequenceholdings/orm 0.1.1 → 0.1.3
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/README.md +12 -2
- package/dist/authoring.cjs +1801 -0
- package/dist/authoring.cjs.map +1 -0
- package/dist/authoring.d.cts +539 -0
- package/dist/authoring.d.ts +539 -0
- package/dist/authoring.js +1759 -0
- package/dist/authoring.js.map +1 -0
- package/dist/canonical.cjs +552 -0
- package/dist/canonical.cjs.map +1 -0
- package/dist/canonical.d.cts +603 -0
- package/dist/canonical.d.ts +603 -0
- package/dist/canonical.js +532 -0
- package/dist/canonical.js.map +1 -0
- package/dist/cli.cjs +7843 -1815
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +8 -1
- package/dist/cli.d.ts +8 -1
- package/dist/cli.js +7854 -1828
- package/dist/cli.js.map +1 -1
- package/dist/client.cjs +120 -0
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +74 -2
- package/dist/client.d.ts +74 -2
- package/dist/client.js +116 -1
- package/dist/client.js.map +1 -1
- package/dist/compiler.cjs +3453 -0
- package/dist/compiler.cjs.map +1 -0
- package/dist/compiler.d.cts +389 -0
- package/dist/compiler.d.ts +389 -0
- package/dist/compiler.js +3396 -0
- package/dist/compiler.js.map +1 -0
- package/dist/drizzle.cjs +14 -0
- package/dist/drizzle.cjs.map +1 -0
- package/dist/drizzle.d.cts +1 -0
- package/dist/drizzle.d.ts +1 -0
- package/dist/drizzle.js +3 -0
- package/dist/drizzle.js.map +1 -0
- package/dist/dynamic-input.cjs +391 -0
- package/dist/dynamic-input.cjs.map +1 -0
- package/dist/dynamic-input.d.cts +111 -0
- package/dist/dynamic-input.d.ts +111 -0
- package/dist/dynamic-input.js +380 -0
- package/dist/dynamic-input.js.map +1 -0
- package/dist/index.cjs +3727 -344
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +236 -59
- package/dist/index.d.ts +236 -59
- package/dist/index.js +3757 -482
- package/dist/index.js.map +1 -1
- package/dist/ir.cjs +642 -0
- package/dist/ir.cjs.map +1 -0
- package/dist/ir.d.cts +1119 -0
- package/dist/ir.d.ts +1119 -0
- package/dist/ir.js +623 -0
- package/dist/ir.js.map +1 -0
- package/dist/naming-CTvWSRTf.d.ts +79 -0
- package/dist/naming-DRyinU4u.d.cts +79 -0
- package/dist/predicate-v2.cjs +1093 -0
- package/dist/predicate-v2.cjs.map +1 -0
- package/dist/predicate-v2.d.cts +139 -0
- package/dist/predicate-v2.d.ts +139 -0
- package/dist/predicate-v2.js +1083 -0
- package/dist/predicate-v2.js.map +1 -0
- package/dist/sdl.cjs +1122 -0
- package/dist/sdl.cjs.map +1 -0
- package/dist/sdl.d.cts +94 -0
- package/dist/sdl.d.ts +94 -0
- package/dist/sdl.js +1098 -0
- package/dist/sdl.js.map +1 -0
- package/package.json +51 -3
- package/src/codegen-gql/apollo-hooks.cjs +98 -0
- package/src/codegen-gql/server-fns.cjs +101 -0
- package/templates/README.md +13 -0
- package/templates/orm-namespace/CLAUDE.md +82 -0
- package/templates/orm-namespace/codegen.ts +22 -0
- package/templates/orm-namespace/gitignore +5 -0
- package/templates/orm-namespace/graphql/example/queries.ts +56 -0
- package/templates/orm-namespace/package.json +24 -0
- package/templates/orm-namespace/schema/tables.ts +71 -0
- package/templates/orm-namespace/sequence.config.ts +32 -0
- package/templates/orm-namespace/tsconfig.json +15 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Sequence ORM namespace
|
|
2
|
+
|
|
3
|
+
This directory defines ONE Sequence ORM namespace: TypeScript table
|
|
4
|
+
definitions with row-level policies, plus handwritten GraphQL operations.
|
|
5
|
+
`seq-studio orm apply` pushes it to Atlas, which validates, compiles, and
|
|
6
|
+
applies it. Your app never talks to Postgres — it calls the data plane with
|
|
7
|
+
`{ op: <hash>, variables }` through the generated hooks/functions.
|
|
8
|
+
|
|
9
|
+
## The loop
|
|
10
|
+
|
|
11
|
+
1. Edit `schema/*.ts` (tables, columns, links, policies) and/or
|
|
12
|
+
`graphql/**/*.ts` (operations).
|
|
13
|
+
2. `seq-studio orm generate` — refreshes `schema.graphql` + generated types.
|
|
14
|
+
3. `seq-studio orm diff` — preview the migration for a schema change.
|
|
15
|
+
4. `seq-studio orm apply -e <env>` — pushes. Schema changes write a reviewed
|
|
16
|
+
migration file into `migrations/`; operation-only edits take the fast path
|
|
17
|
+
(seconds, no migration).
|
|
18
|
+
5. Commit `schema/`, `graphql/`, `schema.graphql`, `generated/`, and
|
|
19
|
+
`migrations/` together.
|
|
20
|
+
|
|
21
|
+
## Hard rules
|
|
22
|
+
|
|
23
|
+
- **Never hand-edit `migrations/`.** Migration files are generated by
|
|
24
|
+
`seq-studio orm apply`/`diff` and replayed verbatim by the server. Editing
|
|
25
|
+
or deleting a committed migration breaks the ledger.
|
|
26
|
+
- **Never hand-edit `schema.graphql` or `generated/`.** Both are outputs of
|
|
27
|
+
`seq-studio orm generate`. To change the API surface, change `schema/*.ts`.
|
|
28
|
+
- **No raw SQL.** There is no query escape hatch: every read/write is a
|
|
29
|
+
GraphQL document in `graphql/`, compiled at push time. For genuinely exotic
|
|
30
|
+
multi-statement writes, declare a table `action` — parameterized, gated,
|
|
31
|
+
audited.
|
|
32
|
+
- **Every table needs row policies.** At minimum declare `read` plus either a
|
|
33
|
+
legacy `write` policy or split `insert` / `update` policies when create and
|
|
34
|
+
resolve are different actors. Policies are the row security boundary and are
|
|
35
|
+
compiled into every operation touching the table. There is no "no policy"
|
|
36
|
+
fallback — an unrestricted table must say so explicitly (`allow: true`
|
|
37
|
+
variant or a `true` policy) so the decision is reviewable.
|
|
38
|
+
- **Exactly one `defineNamespace`** (in `sequence.config.ts`), and every
|
|
39
|
+
table module must call `defineTable` at import time — the CLI collects
|
|
40
|
+
registrations by importing your files.
|
|
41
|
+
- **Renames need hints.** Renaming a column or table without
|
|
42
|
+
`.renamedFrom('old_name')` (column) / `renamedFrom` (table config) is a
|
|
43
|
+
DROP + CREATE — data loss. The diff will refuse ambiguous renames; add the
|
|
44
|
+
hint instead of forcing.
|
|
45
|
+
- **Capabilities/roles are declared here, granted in the Permissions UI.**
|
|
46
|
+
Never hardcode user identities in policies; use `claim('sub')` /
|
|
47
|
+
`claim('org')` and grants.
|
|
48
|
+
|
|
49
|
+
## Policy DSL quick reference
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
author_sub = claim('sub') -- row value vs caller claim
|
|
53
|
+
org = claim('org') and status = 'open' -- boolean algebra
|
|
54
|
+
exists(deal, org = claim('org')) -- traverse a declared link
|
|
55
|
+
capability('deals-read') -- boolean grant check
|
|
56
|
+
role('deal-manager') -- boolean role check
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Variants widen access per grant (permissive union):
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
policies: {
|
|
63
|
+
read: {
|
|
64
|
+
default: "author_sub = claim('sub')",
|
|
65
|
+
variants: [
|
|
66
|
+
{ when: "capability('deals-read')", policy: "org = claim('org')" },
|
|
67
|
+
{ when: "role('deal-manager')", allow: true },
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## GraphQL operations
|
|
74
|
+
|
|
75
|
+
- Named operations only; one export per document; gate with
|
|
76
|
+
`@requires(capability: "…")` / `@requires(role: "…")`.
|
|
77
|
+
- Dynamic filters are policy-bounded: `$where: <Table>BoolExp` /
|
|
78
|
+
`$orderBy` / whole-input write objects are validated against a
|
|
79
|
+
schema-derived whitelist and AND-ed with the table policy at runtime —
|
|
80
|
+
they can never widen access.
|
|
81
|
+
- Joins follow declared links (`toOne` / `toMany` / `toPlatformUser`) — there
|
|
82
|
+
are no ad-hoc joins at query time. Declare the link, then select through it.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CodegenConfig } from '@graphql-codegen/cli'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* graphql-codegen config. `seq-studio orm generate` writes ./schema.graphql
|
|
5
|
+
* from your table definitions, then runs this config when present — emitting
|
|
6
|
+
* typed Apollo hooks (`use<Name>Query`) and server functions (`db<Name>`)
|
|
7
|
+
* over the persisted-operation client.
|
|
8
|
+
*
|
|
9
|
+
* schema.graphql and the generated output are committed: reviews see the API
|
|
10
|
+
* surface change alongside the schema change that caused it.
|
|
11
|
+
*/
|
|
12
|
+
const config: CodegenConfig = {
|
|
13
|
+
schema: './schema.graphql',
|
|
14
|
+
documents: ['graphql/**/*.ts'],
|
|
15
|
+
generates: {
|
|
16
|
+
'./generated/': {
|
|
17
|
+
preset: '@sequenceholdings/orm/codegen',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default config
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handwritten GraphQL operations. `seq-studio orm apply` compiles every
|
|
3
|
+
* exported document at push time; the wire carries only `{ op: <hash>,
|
|
4
|
+
* variables }` — never query text, never SQL. Type names come from the
|
|
5
|
+
* generated `schema.graphql` (`seq-studio orm generate`).
|
|
6
|
+
*
|
|
7
|
+
* Gate an operation with `@requires(capability: "…")` or
|
|
8
|
+
* `@requires(role: "…")` — checked before execution, on top of the
|
|
9
|
+
* table policies that are always compiled in.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const GetOpenDeals = /* GraphQL */ `
|
|
13
|
+
query GetOpenDeals($status: DealStatus!) @requires(capability: "deals-read") {
|
|
14
|
+
deals(where: { status: { _eq: $status } }, orderBy: [{ createdAt: DESC }], limit: 20) {
|
|
15
|
+
id
|
|
16
|
+
title
|
|
17
|
+
amount
|
|
18
|
+
author {
|
|
19
|
+
name
|
|
20
|
+
email
|
|
21
|
+
}
|
|
22
|
+
comments(limit: 3, orderBy: [{ createdAt: DESC }]) {
|
|
23
|
+
id
|
|
24
|
+
body
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
`
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Policy-bounded dynamic arguments (§4f): the filter tree is built by the
|
|
32
|
+
* caller at runtime, validated against the schema-derived whitelist, and
|
|
33
|
+
* always AND-ed with the table's read policy — clients can only narrow
|
|
34
|
+
* within policy-visible rows.
|
|
35
|
+
*/
|
|
36
|
+
export const SearchDeals = /* GraphQL */ `
|
|
37
|
+
query SearchDeals($where: DealBoolExp, $orderBy: [DealOrderBy!], $limit: Int)
|
|
38
|
+
@requires(capability: "deals-read") {
|
|
39
|
+
deals(where: $where, orderBy: $orderBy, limit: $limit) {
|
|
40
|
+
id
|
|
41
|
+
title
|
|
42
|
+
status
|
|
43
|
+
amount
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
`
|
|
47
|
+
|
|
48
|
+
export const AddComment = /* GraphQL */ `
|
|
49
|
+
mutation AddComment($dealId: uuid!, $body: String!) {
|
|
50
|
+
insertCommentsOne(object: { dealId: $dealId, body: $body }) {
|
|
51
|
+
id
|
|
52
|
+
body
|
|
53
|
+
createdAt
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
`
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-namespace",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"validate": "seq-studio orm validate",
|
|
7
|
+
"generate": "seq-studio orm generate",
|
|
8
|
+
"diff": "seq-studio orm diff",
|
|
9
|
+
"apply": "seq-studio orm apply",
|
|
10
|
+
"type-check": "tsc --noEmit"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@apollo/client": "^3.11.0",
|
|
14
|
+
"@sequenceholdings/orm": "^2.0.0",
|
|
15
|
+
"graphql": "^16.11.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@graphql-codegen/cli": "^5.0.7",
|
|
19
|
+
"typescript": "^5.6.0"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=20"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { defineEnum, defineTable, t, toMany, toOne, toPlatformUser } from '@sequenceholdings/orm'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tables are TypeScript, but they SERIALIZE to data — the server never runs
|
|
5
|
+
* this file. Property names are camelCase and become snake_case Postgres
|
|
6
|
+
* columns (`authorSub` → `author_sub`); pass a string to the builder to
|
|
7
|
+
* override (`t.text('legacy_name')`).
|
|
8
|
+
*
|
|
9
|
+
* Every table carries its own row-level policies. The policy DSL:
|
|
10
|
+
* - `claim('sub')` / `claim('org')` — the caller's identity claims
|
|
11
|
+
* - `capability('x')` / `role('y')` — boolean grants, resolved per caller
|
|
12
|
+
* - `exists(link, <predicate>)` — traverse a declared link
|
|
13
|
+
* - variants: `default` applies to everyone; each variant WIDENS access
|
|
14
|
+
* when its `when` guard matches (permissive union — grants only ever
|
|
15
|
+
* widen, never narrow).
|
|
16
|
+
*/
|
|
17
|
+
export const dealStatus = defineEnum('deal_status', ['open', 'won', 'lost'])
|
|
18
|
+
|
|
19
|
+
export const deals = defineTable(
|
|
20
|
+
'deals',
|
|
21
|
+
{
|
|
22
|
+
id: t.uuid().primaryKey().default('gen_random_uuid()'),
|
|
23
|
+
title: t.text().notNull(),
|
|
24
|
+
status: t.enum(dealStatus).notNull().default('open'),
|
|
25
|
+
amount: t.numeric(),
|
|
26
|
+
org: t.text().notNull().description('Organization the deal belongs to'),
|
|
27
|
+
authorSub: t.text().notNull().description('Auth0 sub of the creator'),
|
|
28
|
+
createdAt: t.timestamptz().notNull().default('now()'),
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
description: 'Deals owned by their author, readable org-wide with the deals-read capability',
|
|
32
|
+
indexes: [{ columns: ['org'] }, { columns: ['authorSub'] }],
|
|
33
|
+
links: {
|
|
34
|
+
comments: toMany(() => comments, { on: 'dealId' }),
|
|
35
|
+
author: toPlatformUser({ on: 'authorSub' }),
|
|
36
|
+
},
|
|
37
|
+
policies: {
|
|
38
|
+
read: {
|
|
39
|
+
default: "author_sub = claim('sub')",
|
|
40
|
+
variants: [
|
|
41
|
+
{ when: "capability('deals-read')", policy: "org = claim('org')" },
|
|
42
|
+
{ when: "role('deal-manager')", allow: true },
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
write: "author_sub = claim('sub')",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
export const comments = defineTable(
|
|
51
|
+
'comments',
|
|
52
|
+
{
|
|
53
|
+
id: t.uuid().primaryKey().default('gen_random_uuid()'),
|
|
54
|
+
dealId: t.uuid().notNull(),
|
|
55
|
+
body: t.text().notNull(),
|
|
56
|
+
authorSub: t.text().notNull(),
|
|
57
|
+
createdAt: t.timestamptz().notNull().default('now()'),
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
description: 'Comments on deals — visibility follows the parent deal',
|
|
61
|
+
indexes: [{ columns: ['dealId'] }],
|
|
62
|
+
links: {
|
|
63
|
+
deal: toOne(() => deals, { on: 'dealId' }),
|
|
64
|
+
author: toPlatformUser({ on: 'authorSub' }),
|
|
65
|
+
},
|
|
66
|
+
policies: {
|
|
67
|
+
read: "exists(deal, org = claim('org'))",
|
|
68
|
+
write: "author_sub = claim('sub')",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { defineNamespace } from '@sequenceholdings/orm'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The namespace config — exactly one per directory.
|
|
5
|
+
*
|
|
6
|
+
* Capabilities and roles are DECLARED here (verbatim custom-roles grammar);
|
|
7
|
+
* who HOLDS them is granted later in the Permissions UI (FGA), never in code.
|
|
8
|
+
* Table policies reference them with `capability('…')` / `role('…')`.
|
|
9
|
+
*/
|
|
10
|
+
export default defineNamespace({
|
|
11
|
+
// Rename me: the namespace becomes your Postgres schema (orm_<namespace>).
|
|
12
|
+
namespace: 'my_namespace',
|
|
13
|
+
description: 'Describe what this namespace stores and who uses it.',
|
|
14
|
+
capabilities: {
|
|
15
|
+
roles: {
|
|
16
|
+
definitions: {
|
|
17
|
+
'deal-manager': {
|
|
18
|
+
description: 'Full access to every deal in the namespace',
|
|
19
|
+
default: false,
|
|
20
|
+
inherits: [],
|
|
21
|
+
grants: ['deals-read'],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
capabilities: {
|
|
25
|
+
'deals-read': {
|
|
26
|
+
description: 'Read all deals in the caller org (instead of author-only)',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
uses: [],
|
|
31
|
+
},
|
|
32
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": ["ES2022", "DOM"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"isolatedModules": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["sequence.config.ts", "codegen.ts", "schema", "graphql", "generated"]
|
|
15
|
+
}
|