foldkit-cms-drizzle 0.1.2 → 0.3.0
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 +30 -4
- package/dist/index.d.mts +23 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +93 -29
- package/dist/index.mjs.map +1 -1
- package/package.json +14 -12
package/README.md
CHANGED
|
@@ -90,6 +90,27 @@ RemoteServer.make({
|
|
|
90
90
|
- A content type with no `published` role has nothing to hide, and needs none.
|
|
91
91
|
- **`create` and `update` go to the CMS, not to `RemoteServer.make`.** Registered
|
|
92
92
|
there too, they are a way to publish with no draft, no revision and no `allow`.
|
|
93
|
+
- **Content that exists already is imported, not inserted.** `cms.import({ type,
|
|
94
|
+
values, as, at, entry })` makes the entry and a draft of `values`, then publishes
|
|
95
|
+
it as a publish does: your `create` handler writes the row, as `as`, and revision
|
|
96
|
+
1 records `values`, dated `at`. It is one transaction, and the server's own act,
|
|
97
|
+
so `allow` is not asked. Writing the CMS's tables by hand has to match what a
|
|
98
|
+
publish writes, and drifts.
|
|
99
|
+
|
|
100
|
+
### Seeding
|
|
101
|
+
|
|
102
|
+
A seed is a runner over `cms.import` — one item per entry, in dependency order:
|
|
103
|
+
|
|
104
|
+
- **Fix the clock.** Pass `now` to `CmsServer.make` and `at` to each import,
|
|
105
|
+
so every run writes the same dates: the build's pages and every sandbox agree.
|
|
106
|
+
- **Name entries that are pointed at.** Pass `entry` where another import names
|
|
107
|
+
it (a page naming its featured post), and import what is pointed at first so
|
|
108
|
+
the id is known. Anything else takes the default new id.
|
|
109
|
+
- **Fix `as` once.** Import as an editor and vary the rest; the input is
|
|
110
|
+
`ImportItem<P>` less `as`, so the runner and the tool cannot drift.
|
|
111
|
+
|
|
112
|
+
The CMS example's `seed.ts` is the worked shape: dated posts, then the pages
|
|
113
|
+
that point at them.
|
|
93
114
|
|
|
94
115
|
## Verify the audience boundary
|
|
95
116
|
|
|
@@ -156,8 +177,9 @@ fails after writing leaves nothing behind.
|
|
|
156
177
|
- A draft your mutation's Input refuses is not published, and the error says why.
|
|
157
178
|
The draft is kept.
|
|
158
179
|
- A row that is already shown keeps the date it was first published on.
|
|
159
|
-
-
|
|
160
|
-
|
|
180
|
+
- The row as your handler left it goes to the client with the entry and the
|
|
181
|
+
revision, every column of it, so a handler need not patch what it wrote. What
|
|
182
|
+
your handler returns (patches, connection changes, deletions) goes too.
|
|
161
183
|
|
|
162
184
|
### Slugs
|
|
163
185
|
|
|
@@ -245,12 +267,16 @@ your `now`. An overdue scheduled publish reads overdue, with its reason.
|
|
|
245
267
|
| `sqliteTables()`, `pgTables()` | The three tables, per dialect. `sqliteSchema` is their `create table` statements. |
|
|
246
268
|
| `published(column, isAuthor)` | A `visible` rule for a content table: a visitor sees rows whose column is set. |
|
|
247
269
|
| `Transaction.statements`, `Transaction.drizzle` | How a publish is made whole, by driver. |
|
|
248
|
-
| `CmsServer.make({ tables, content, transaction, isAuthor, allow?, now?, nameOf?, maxDraftSize? })` | `sources`, `queries`, `mutations`, `due`, and `bindings`. |
|
|
270
|
+
| `CmsServer.make({ tables, content, transaction, isAuthor, allow?, now?, nameOf?, maxDraftSize? })` | `sources`, `queries`, `mutations`, `due`, `import`, and `bindings`. |
|
|
249
271
|
| `cms.due(now, { as })` | Publishes what has come due; an Effect of `{ entry, error }` each. |
|
|
272
|
+
| `cms.import({ type, values, as, at?, entry? })` | Content that is published already (a seed, or what another CMS held), written by the publish path; an Effect of `{ entry, targetId }`. |
|
|
250
273
|
|
|
251
274
|
## Limits
|
|
252
275
|
|
|
253
|
-
- `allow` is asked about every transition
|
|
276
|
+
- `allow` is asked about every transition, and again when an entry is read with
|
|
277
|
+
`may`: the entry's `may` is the transitions `allow` lets the reader ask
|
|
278
|
+
(`Cms.transitions` filtered), so the client can leave out what would be
|
|
279
|
+
refused. Whether the entry offers one now is its `state`'s to say.
|
|
254
280
|
- `CmsPublish` needs a draft. To show an unpublished row again as it is, the
|
|
255
281
|
editor's publish saves what is there first; a client of your own does the same.
|
|
256
282
|
- A driver's refusal is recognised by its words (`unique` or `duplicate`, and the
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { AnyColumn } from "drizzle-orm";
|
|
2
2
|
import { Effect, Schema } from "effect";
|
|
3
|
-
import { Content } from "foldkit-cms";
|
|
3
|
+
import { Content, Transition } from "foldkit-cms";
|
|
4
4
|
import { AnyEntityBinding, DrizzleDatabase, Visible } from "foldkit-remote-drizzle";
|
|
5
|
-
import { EntitySource, MutationSource, QuerySource } from "foldkit-remote-server";
|
|
5
|
+
import { EntitySource, MutationSource, QuerySource, RemoteServerError } from "foldkit-remote-server";
|
|
6
6
|
//#region src/tables.d.ts
|
|
7
7
|
/**
|
|
8
8
|
* - `cms_entries`: a piece of content from first keystroke to archive. It exists
|
|
@@ -816,8 +816,6 @@ export interface ServedContent<P = any> {
|
|
|
816
816
|
readonly create: MutationSource<P, DrizzleDatabase>;
|
|
817
817
|
readonly update: MutationSource<P, DrizzleDatabase>;
|
|
818
818
|
}
|
|
819
|
-
/** What an author may be refused, by `allow`. */
|
|
820
|
-
export type Asked = 'save' | 'discard' | 'publish' | 'unpublish' | 'schedule' | 'unschedule' | 'archive' | 'unarchive' | 'restore';
|
|
821
819
|
/**
|
|
822
820
|
* Runs some work so that it happened entirely or did not. Drizzle's own
|
|
823
821
|
* transactions differ by driver, so the application says which this is.
|
|
@@ -849,7 +847,7 @@ export interface CmsServerConfig<P> {
|
|
|
849
847
|
* Whether this author may make this transition. It is asked after the entry is
|
|
850
848
|
* found and the transition is one its state offers. Default: any author may.
|
|
851
849
|
*/
|
|
852
|
-
readonly allow?: (principal: P, transition:
|
|
850
|
+
readonly allow?: (principal: P, transition: Transition, entry: EntryRow) => boolean;
|
|
853
851
|
/** The server's clock, passed in so a test can hold it. */
|
|
854
852
|
readonly now?: () => Date;
|
|
855
853
|
/**
|
|
@@ -870,6 +868,21 @@ export interface EntryRow {
|
|
|
870
868
|
readonly archivedAt: string | null;
|
|
871
869
|
readonly revision: number | null;
|
|
872
870
|
}
|
|
871
|
+
/**
|
|
872
|
+
* One import: content published already, such as a site's first pages or what
|
|
873
|
+
* another CMS held. `as` is whom it is imported as (an editor, so the create
|
|
874
|
+
* handler writes the row); a runner that imports many fixes `as` once and
|
|
875
|
+
* varies the rest. `at` is when it was published (default now); `entry`
|
|
876
|
+
* names the entry (default a new id) — name it where another import points
|
|
877
|
+
* at it, and import what is pointed at first.
|
|
878
|
+
*/
|
|
879
|
+
export interface ImportItem<P> {
|
|
880
|
+
readonly type: string;
|
|
881
|
+
readonly values: unknown;
|
|
882
|
+
readonly as: P;
|
|
883
|
+
readonly at?: Date | undefined;
|
|
884
|
+
readonly entry?: string | undefined;
|
|
885
|
+
}
|
|
873
886
|
/**
|
|
874
887
|
* For a content table's binding: a visitor sees the rows whose `published`
|
|
875
888
|
* column is set, and an author sees every row. Give it as the storage's
|
|
@@ -893,6 +906,10 @@ export declare const CmsServer: {
|
|
|
893
906
|
readonly entry: string;
|
|
894
907
|
readonly error: string | null;
|
|
895
908
|
}[], never, DrizzleDatabase>;
|
|
909
|
+
import: (item: ImportItem<P>) => Effect.Effect<{
|
|
910
|
+
entry: string;
|
|
911
|
+
targetId: string;
|
|
912
|
+
}, RemoteServerError, DrizzleDatabase>;
|
|
896
913
|
/** The bindings of `Entry`, `Draft` and `Revision`, for a handler that returns patches of them. */
|
|
897
914
|
bindings: {
|
|
898
915
|
readonly Entry: import("foldkit-remote-drizzle").EntityBinding<"CmsEntry", import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
|
|
@@ -1172,6 +1189,7 @@ export declare const CmsServer: {
|
|
|
1172
1189
|
readonly error: Schema.NullOr<Schema.String>;
|
|
1173
1190
|
}>>;
|
|
1174
1191
|
}>;
|
|
1192
|
+
readonly may: Schema.$Array<Schema.Literals<readonly ["save", "discard", "publish", "schedule", "unschedule", "unpublish", "archive", "unarchive", "restore"]>>;
|
|
1175
1193
|
} & {
|
|
1176
1194
|
readonly draft: Schema.NullOr<Schema.Codec<import("foldkit-remote").EntityRef<"CmsDraft", Schema.Struct.Fields>, `CmsDraft:${string}`, never, never>>;
|
|
1177
1195
|
readonly revisions: Schema.$Array<Schema.Codec<import("foldkit-remote").EntityRef<"CmsRevision", Schema.Struct.Fields>, `CmsRevision:${string}`, never, never>>;
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/tables.ts","../src/index.ts"],"mappings":";;;;;;;;;;;;qBAea;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAiCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAkCA;KAgBD,YAAY,kBAAkB,gBAAgB,kBAAkB;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/tables.ts","../src/index.ts"],"mappings":";;;;;;;;;;;;qBAea;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAiCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAkCA;KAgBD,YAAY,kBAAkB,gBAAgB,kBAAkB;;;;;;;;iBC1C3D,cAAc;WACpB,MAAM;WACN,SAAS;WACT,QAAQ,eAAe,GAAG;WAC1B,QAAQ,eAAe,GAAG;;;;;;YAOzB,eAAe,GAAG,GAC5B,MAAM,OAAO,OAAO,GAAG,GAAG,qBACvB,OAAO,OAAO,GAAG,GAAG;qBAyBZ;;;;;;;;EAiBF,YAAA;;;;;EA0BA,SAAA;;iBAGM,gBAAgB;WACtB,QAAQ;WACR,SAAS,cAAc,cAAc;;WAErC,aAAa;;WAEb,WAAW,WAAW;;;;;WAKtB,SAAS,WAAW,GAAG,YAAY,YAAY,OAAO;;WAEtD,YAAY;;;;;;WAMZ;;WAEA,UAAU,WAAW;;;iBAIf;WACN;WACA;WACA;WACA;WACA;WACA;;;;;;;;;;iBAWM,WAAW;WACjB;WACA;WACA,IAAI;WACJ,KAAK;WACL;;;;;;;;qBAmCE,YACV,GAAG,QAAQ,WAAW,WAAW,WAAW,kBAAgB;qBAIlD;EACJ,OAAA,aAAC,QAAoB,gBAAgB;;;;;;;IAq6BN,SAAA,cAAc,YAAY,GAAG;;IAvKhD,MAAA,IAAA,MAAI;eAAsB,KAAK,+BAA+B;UAAG,OAAA;eAmBtC;eAAwB;;IAkC3C,SAAA,MAAA,WAAW,OAAE,OAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { and, eq, inArray, isNotNull, isNull, lte, ne, sql } from "drizzle-orm";
|
|
2
2
|
import { Effect, Exit, Schema, Semaphore } from "effect";
|
|
3
3
|
import { Cms } from "foldkit-cms";
|
|
4
|
-
import { DrizzleDatabase, bind, query, returning, source } from "foldkit-remote-drizzle";
|
|
4
|
+
import { DrizzleDatabase, bind, drizzleWrites, query, returning, source } from "foldkit-remote-drizzle";
|
|
5
5
|
import { RemoteServer, RemoteServerError } from "foldkit-remote-server";
|
|
6
6
|
import { integer, jsonb, pgTable, text } from "drizzle-orm/pg-core";
|
|
7
7
|
import { integer as integer$1, sqliteTable, text as text$1 } from "drizzle-orm/sqlite-core";
|
|
@@ -205,7 +205,10 @@ const CmsServer = { make: (config) => {
|
|
|
205
205
|
}]
|
|
206
206
|
}
|
|
207
207
|
},
|
|
208
|
-
derived: {
|
|
208
|
+
derived: {
|
|
209
|
+
state: { supplied: true },
|
|
210
|
+
may: { supplied: true }
|
|
211
|
+
}
|
|
209
212
|
},
|
|
210
213
|
Draft: {
|
|
211
214
|
table: tables.drafts,
|
|
@@ -268,33 +271,41 @@ const CmsServer = { make: (config) => {
|
|
|
268
271
|
...generated,
|
|
269
272
|
read: (context) => Effect.gen(function* () {
|
|
270
273
|
const wantsState = context.fields.includes("state");
|
|
271
|
-
const
|
|
274
|
+
const wantsMay = context.fields.includes("may");
|
|
275
|
+
const fields = context.fields.filter((field) => field !== "state" && field !== "may");
|
|
272
276
|
const records = yield* generated.read({
|
|
273
277
|
...context,
|
|
274
|
-
fields: wantsState ? [.../* @__PURE__ */ new Set([
|
|
278
|
+
fields: wantsState || wantsMay ? [.../* @__PURE__ */ new Set([
|
|
275
279
|
...fields,
|
|
276
280
|
"type",
|
|
277
281
|
"targetId",
|
|
278
|
-
"archivedAt"
|
|
282
|
+
"archivedAt",
|
|
283
|
+
...wantsMay ? ["label", "revision"] : []
|
|
279
284
|
])] : fields
|
|
280
285
|
});
|
|
281
|
-
if (!wantsState) return records;
|
|
282
|
-
const
|
|
286
|
+
if (!wantsState && !wantsMay) return records;
|
|
287
|
+
const rows = records.map((record) => ({
|
|
283
288
|
id: record.id,
|
|
284
289
|
type: String(record.values.type),
|
|
285
290
|
targetId: record.values.targetId ?? null,
|
|
286
|
-
label: "",
|
|
291
|
+
label: String(record.values.label ?? ""),
|
|
287
292
|
archivedAt: record.values.archivedAt ?? null,
|
|
288
|
-
revision: null
|
|
289
|
-
})));
|
|
290
|
-
const at = now();
|
|
291
|
-
return records.map((record) => ({
|
|
292
|
-
id: record.id,
|
|
293
|
-
values: {
|
|
294
|
-
...Object.fromEntries(Object.entries(record.values).filter(([field]) => field === "id" || context.fields.includes(field))),
|
|
295
|
-
state: Cms.state(facts.get(record.id), at)
|
|
296
|
-
}
|
|
293
|
+
revision: record.values.revision ?? null
|
|
297
294
|
}));
|
|
295
|
+
const facts = yield* factsOf(rows);
|
|
296
|
+
const at = now();
|
|
297
|
+
return records.map((record, index) => {
|
|
298
|
+
const row = rows[index];
|
|
299
|
+
const known = facts.get(record.id);
|
|
300
|
+
return {
|
|
301
|
+
id: record.id,
|
|
302
|
+
values: {
|
|
303
|
+
...Object.fromEntries(Object.entries(record.values).filter(([field]) => field === "id" || context.fields.includes(field))),
|
|
304
|
+
...wantsState ? { state: Cms.state(known, at) } : {},
|
|
305
|
+
...wantsMay ? { may: Cms.transitions.filter((transition) => config.allow?.(context.principal, transition, row) !== false) } : {}
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
});
|
|
298
309
|
})
|
|
299
310
|
};
|
|
300
311
|
const worklist = query(Cms.Entries, { entity: Db.Entry });
|
|
@@ -359,7 +370,7 @@ const CmsServer = { make: (config) => {
|
|
|
359
370
|
if (existing?.archivedAt != null) return yield* refuse("An archived entry takes no draft");
|
|
360
371
|
yield* asking(principal, "save", existing);
|
|
361
372
|
if (existing !== void 0) yield* promised(principal, existing);
|
|
362
|
-
const database = yield*
|
|
373
|
+
const database = yield* drizzleWrites;
|
|
363
374
|
const who = nameOf(principal);
|
|
364
375
|
const id = existing?.id ?? input.entry;
|
|
365
376
|
const [held] = yield* readRows(Db.Draft, { updatedAt: tables.drafts.updatedAt }, id);
|
|
@@ -406,7 +417,7 @@ const CmsServer = { make: (config) => {
|
|
|
406
417
|
if (entry === void 0) return yield* refuse("There is no such entry");
|
|
407
418
|
yield* asking(principal, "discard", entry);
|
|
408
419
|
yield* promised(principal, entry);
|
|
409
|
-
const database = yield*
|
|
420
|
+
const database = yield* drizzleWrites;
|
|
410
421
|
yield* Effect.promise(() => Promise.resolve(database.delete(tables.drafts).where(eq(tables.drafts.id, entry.id))));
|
|
411
422
|
const gone = [{
|
|
412
423
|
entity: "CmsDraft",
|
|
@@ -477,8 +488,8 @@ const CmsServer = { make: (config) => {
|
|
|
477
488
|
decoded: yield* Schema.decodeUnknownEffect(handler.Input)(value).pipe(Effect.mapError((error) => refuse(`This draft is not ready to publish: ${String(error)}`)))
|
|
478
489
|
};
|
|
479
490
|
});
|
|
480
|
-
/** A publish, for the operation that asks for one
|
|
481
|
-
const publishing = (found, principal, basedOn) => Effect.gen(function* () {
|
|
491
|
+
/** A publish, for the operation that asks for one, the schedule that comes due, and an import. */
|
|
492
|
+
const publishing = (found, principal, basedOn, at = now()) => Effect.gen(function* () {
|
|
482
493
|
const { entry, served } = found;
|
|
483
494
|
const database = yield* DrizzleDatabase;
|
|
484
495
|
const held = entry.revision;
|
|
@@ -494,7 +505,7 @@ const CmsServer = { make: (config) => {
|
|
|
494
505
|
if ((yield* Effect.promise(() => Promise.resolve(database.select({ id }).from(served.binding.table).where(and(eq(column, slug), creating ? void 0 : ne(id, entry.targetId))).limit(1)))).length > 0) return yield* taken(address.key);
|
|
495
506
|
}
|
|
496
507
|
return yield* Effect.gen(function* () {
|
|
497
|
-
const writes = yield*
|
|
508
|
+
const writes = yield* drizzleWrites;
|
|
498
509
|
const n = (held ?? 0) + 1;
|
|
499
510
|
if ((yield* Effect.promise(() => Promise.resolve(writes.update(tables.entries).set({ revision: n }).where(and(eq(tables.entries.id, entry.id), held === null ? isNull(tables.entries.revision) : eq(tables.entries.revision, held))).returning({ id: tables.entries.id })))).length === 0) return yield* conflict;
|
|
500
511
|
const ran = yield* handler.run({
|
|
@@ -502,7 +513,6 @@ const CmsServer = { make: (config) => {
|
|
|
502
513
|
principal
|
|
503
514
|
});
|
|
504
515
|
const targetId = creating ? String(ran.output.id) : entry.targetId;
|
|
505
|
-
const at = now();
|
|
506
516
|
const shown = served.type.roles.published;
|
|
507
517
|
if (shown !== void 0) {
|
|
508
518
|
const column = served.binding.columns[shown.key];
|
|
@@ -519,7 +529,6 @@ const CmsServer = { make: (config) => {
|
|
|
519
529
|
})));
|
|
520
530
|
yield* Effect.promise(() => Promise.resolve(writes.delete(tables.drafts).where(eq(tables.drafts.id, entry.id))));
|
|
521
531
|
yield* Effect.promise(() => Promise.resolve(writes.update(tables.entries).set({ targetId }).where(eq(tables.entries.id, entry.id)).returning({ id: tables.entries.id })));
|
|
522
|
-
const content = returning(served.binding, shown === void 0 ? [] : [shown.key]);
|
|
523
532
|
return {
|
|
524
533
|
output: {
|
|
525
534
|
entry: entry.id,
|
|
@@ -528,7 +537,7 @@ const CmsServer = { make: (config) => {
|
|
|
528
537
|
},
|
|
529
538
|
entities: [
|
|
530
539
|
...ran.entities,
|
|
531
|
-
...
|
|
540
|
+
...yield* returning.row(served.binding, targetId),
|
|
532
541
|
...yield* entryPatches(entry.id),
|
|
533
542
|
...revisionPatch.patches(yield* readRows(Db.Revision, revisionPatch.columns, revisionId))
|
|
534
543
|
],
|
|
@@ -547,7 +556,7 @@ const CmsServer = { make: (config) => {
|
|
|
547
556
|
}));
|
|
548
557
|
/** Writes to an entry's draft, and answers with both as the client should now hold them. */
|
|
549
558
|
const drafted = (id, values) => Effect.gen(function* () {
|
|
550
|
-
const writes = yield*
|
|
559
|
+
const writes = yield* drizzleWrites;
|
|
551
560
|
yield* Effect.promise(() => Promise.resolve(writes.update(tables.drafts).set(values).where(eq(tables.drafts.id, id)).returning({ id: tables.drafts.id })));
|
|
552
561
|
return {
|
|
553
562
|
output: {},
|
|
@@ -578,7 +587,7 @@ const CmsServer = { make: (config) => {
|
|
|
578
587
|
const archiving = (transition) => operation(transition === "archive" ? Cms.Operations.Archive : Cms.Operations.Unarchive, ({ input, principal }) => Effect.gen(function* () {
|
|
579
588
|
const { entry, served } = yield* offering(input.entry, transition);
|
|
580
589
|
yield* asking(principal, transition, entry);
|
|
581
|
-
const writes = yield*
|
|
590
|
+
const writes = yield* drizzleWrites;
|
|
582
591
|
yield* Effect.promise(() => Promise.resolve(writes.update(tables.entries).set({ archivedAt: transition === "archive" ? now().toISOString() : null }).where(eq(tables.entries.id, entry.id)).returning({ id: tables.entries.id })));
|
|
583
592
|
const shown = served.type.roles.published;
|
|
584
593
|
if (transition === "unarchive" || shown === void 0 || entry.targetId === null) return {
|
|
@@ -605,7 +614,7 @@ const CmsServer = { make: (config) => {
|
|
|
605
614
|
const database = yield* DrizzleDatabase;
|
|
606
615
|
const [revision] = yield* Effect.promise(() => Promise.resolve(database.select({ values: tables.revisions.values }).from(tables.revisions).where(and(eq(tables.revisions.entryId, entry.id), eq(tables.revisions.n, input.revision))).limit(1)));
|
|
607
616
|
if (revision === void 0) return yield* refuse(`This entry has no revision ${input.revision}`);
|
|
608
|
-
const writes =
|
|
617
|
+
const writes = yield* drizzleWrites;
|
|
609
618
|
const [held] = yield* readRows(Db.Draft, { updatedAt: tables.drafts.updatedAt }, entry.id);
|
|
610
619
|
const at = now().toISOString();
|
|
611
620
|
const updatedAt = held !== void 0 && at <= String(held.updatedAt) ? new Date(new Date(String(held.updatedAt)).getTime() + 1).toISOString() : at;
|
|
@@ -665,11 +674,65 @@ const CmsServer = { make: (config) => {
|
|
|
665
674
|
}
|
|
666
675
|
return outcomes;
|
|
667
676
|
});
|
|
677
|
+
/**
|
|
678
|
+
* Content published already, such as a site's first pages or what another CMS
|
|
679
|
+
* held, written by the path a publish takes: an entry and a draft of
|
|
680
|
+
* `values`, then the publish, which runs the type's own `create` handler as
|
|
681
|
+
* `as` and records revision 1. It is the server's act, not an author's, so
|
|
682
|
+
* `allow` is not asked. `at` is when it was published (default now); `entry`
|
|
683
|
+
* names the entry (default a new id). One transaction: it all lands or none.
|
|
684
|
+
*/
|
|
685
|
+
const importing = (item) => config.transaction(Effect.gen(function* () {
|
|
686
|
+
const served = byType.get(item.type);
|
|
687
|
+
if (served === void 0) return yield* refuse(`"${item.type}" is not a type of content this server knows`);
|
|
688
|
+
const writes = yield* drizzleWrites;
|
|
689
|
+
const id = item.entry ?? Cms.newEntryId();
|
|
690
|
+
const at = item.at ?? now();
|
|
691
|
+
const labelKey = served.type.roles.label?.key;
|
|
692
|
+
const named = labelKey === void 0 ? void 0 : item.values?.[labelKey];
|
|
693
|
+
const entry = {
|
|
694
|
+
id,
|
|
695
|
+
type: item.type,
|
|
696
|
+
targetId: null,
|
|
697
|
+
label: typeof named === "string" ? named : "",
|
|
698
|
+
archivedAt: null,
|
|
699
|
+
revision: null
|
|
700
|
+
};
|
|
701
|
+
yield* Effect.promise(() => Promise.resolve(writes.insert(tables.entries).values({
|
|
702
|
+
...entry,
|
|
703
|
+
createdBy: nameOf(item.as),
|
|
704
|
+
createdAt: at.toISOString()
|
|
705
|
+
})));
|
|
706
|
+
yield* Effect.promise(() => Promise.resolve(writes.insert(tables.drafts).values({
|
|
707
|
+
id,
|
|
708
|
+
values: item.values,
|
|
709
|
+
model: null,
|
|
710
|
+
form: "",
|
|
711
|
+
updatedAt: at.toISOString(),
|
|
712
|
+
updatedBy: nameOf(item.as),
|
|
713
|
+
baseRevision: null
|
|
714
|
+
})));
|
|
715
|
+
return {
|
|
716
|
+
entry: id,
|
|
717
|
+
targetId: (yield* publishing({
|
|
718
|
+
entry,
|
|
719
|
+
served,
|
|
720
|
+
facts: {
|
|
721
|
+
archivedAt: null,
|
|
722
|
+
row: "none",
|
|
723
|
+
draft: {
|
|
724
|
+
scheduledFor: null,
|
|
725
|
+
scheduleError: null
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}, item.as, null, at)).output.targetId
|
|
729
|
+
};
|
|
730
|
+
}));
|
|
668
731
|
const Unpublish = operation(Cms.Operations.Unpublish, ({ input, principal }) => Effect.gen(function* () {
|
|
669
732
|
const { entry, served } = yield* offering(input.entry, "unpublish");
|
|
670
733
|
yield* asking(principal, "unpublish", entry);
|
|
671
734
|
const shown = served.type.roles.published;
|
|
672
|
-
const writes = yield*
|
|
735
|
+
const writes = yield* drizzleWrites;
|
|
673
736
|
yield* Effect.promise(() => Promise.resolve(writes.update(served.binding.table).set({ [shown.key]: null }).where(eq(served.binding.columns.id, entry.targetId)).returning({ id: served.binding.columns.id })));
|
|
674
737
|
const content = returning(served.binding, [shown.key]);
|
|
675
738
|
return {
|
|
@@ -704,6 +767,7 @@ const CmsServer = { make: (config) => {
|
|
|
704
767
|
queries: [worklist, ...config.content.flatMap(({ type, binding }) => type.roles.slug === void 0 ? [] : [query(Cms.bySlug(type), { entity: binding })])],
|
|
705
768
|
mutations,
|
|
706
769
|
due,
|
|
770
|
+
import: importing,
|
|
707
771
|
/** The bindings of `Entry`, `Draft` and `Revision`, for a handler that returns patches of them. */
|
|
708
772
|
bindings: Db
|
|
709
773
|
};
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["text","integer","pgText","pgInteger"],"sources":["../src/tables.ts","../src/index.ts"],"sourcesContent":["/**\n * The three tables the CMS keeps beside the application's own. Drizzle tables\n * are made per dialect, so there is one definition for each; they have the same\n * columns. An application adds them to its schema and its migrations as it does\n * any table, and may define its own instead, so long as the columns are these.\n */\nimport { integer as pgInteger, jsonb, pgTable, text as pgText } from 'drizzle-orm/pg-core'\nimport { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'\n\n/**\n * - `cms_entries`: a piece of content from first keystroke to archive. It exists\n * before its content row does.\n * - `cms_drafts`: an entry's one working copy; its id is the entry's.\n * - `cms_revisions`: published values, append-only.\n */\nexport const sqliteTables = () => ({\n entries: sqliteTable('cms_entries', {\n id: text('id').primaryKey(),\n type: text('type').notNull(),\n targetId: text('target_id'),\n label: text('label').notNull(),\n createdBy: text('created_by'),\n createdAt: text('created_at').notNull(),\n archivedAt: text('archived_at'),\n revision: integer('revision'),\n }),\n drafts: sqliteTable('cms_drafts', {\n id: text('id').primaryKey(),\n values: text('values', { mode: 'json' }),\n model: text('model', { mode: 'json' }),\n form: text('form').notNull(),\n updatedAt: text('updated_at').notNull(),\n updatedBy: text('updated_by'),\n baseRevision: integer('base_revision'),\n scheduledFor: text('scheduled_for'),\n scheduledBy: text('scheduled_by'),\n scheduleError: text('schedule_error'),\n }),\n revisions: sqliteTable('cms_revisions', {\n id: text('id').primaryKey(),\n entryId: text('entry_id').notNull(),\n n: integer('n').notNull(),\n values: text('values', { mode: 'json' }),\n publishedAt: text('published_at').notNull(),\n publishedBy: text('published_by'),\n }),\n})\n\nexport const pgTables = () => ({\n entries: pgTable('cms_entries', {\n id: pgText('id').primaryKey(),\n type: pgText('type').notNull(),\n targetId: pgText('target_id'),\n label: pgText('label').notNull(),\n createdBy: pgText('created_by'),\n createdAt: pgText('created_at').notNull(),\n archivedAt: pgText('archived_at'),\n revision: pgInteger('revision'),\n }),\n drafts: pgTable('cms_drafts', {\n id: pgText('id').primaryKey(),\n values: jsonb('values'),\n model: jsonb('model'),\n form: pgText('form').notNull(),\n updatedAt: pgText('updated_at').notNull(),\n updatedBy: pgText('updated_by'),\n baseRevision: pgInteger('base_revision'),\n scheduledFor: pgText('scheduled_for'),\n scheduledBy: pgText('scheduled_by'),\n scheduleError: pgText('schedule_error'),\n }),\n revisions: pgTable('cms_revisions', {\n id: pgText('id').primaryKey(),\n entryId: pgText('entry_id').notNull(),\n n: pgInteger('n').notNull(),\n values: jsonb('values'),\n publishedAt: pgText('published_at').notNull(),\n publishedBy: pgText('published_by'),\n }),\n})\n\n/** The `create table` statements for SQLite, for a test or a first migration. */\nexport const sqliteSchema = `\ncreate table if not exists cms_entries (\n id text primary key, type text not null, target_id text, label text not null,\n created_by text, created_at text not null, archived_at text, revision integer\n);\ncreate table if not exists cms_drafts (\n id text primary key, \"values\" text, model text, form text not null,\n updated_at text not null, updated_by text, base_revision integer,\n scheduled_for text, scheduled_by text, schedule_error text\n);\ncreate table if not exists cms_revisions (\n id text primary key, entry_id text not null, n integer not null, \"values\" text,\n published_at text not null, published_by text\n);\n`\n\nexport type CmsTables = ReturnType<typeof sqliteTables> | ReturnType<typeof pgTables>\n","/**\n * `foldkit-cms-drizzle` — the server half of `foldkit-cms`.\n *\n * It keeps an author's unpublished work in three tables beside the\n * application's own, and it is where the audience boundary is enforced: a\n * principal that is not an author is refused entries, drafts and revisions\n * outright, and sees of the content only the rows its `published` role shows.\n * Both are a binding's `visible`, so they hold on every path a table is read by.\n *\n * Publishing runs the application's own mutation with the draft's value, inside\n * a transaction that also appends the revision and removes the draft, so a\n * publish happened entirely or did not happen.\n */\nimport {\n and,\n eq,\n inArray,\n isNotNull,\n isNull,\n lte,\n ne,\n sql,\n type AnyColumn,\n type SQL,\n} from 'drizzle-orm'\nimport { Effect, Exit, Schema, Semaphore } from 'effect'\nimport { Cms, type Content, type Facts } from 'foldkit-cms'\nimport type { MutationDescriptor } from 'foldkit-remote'\nimport {\n DrizzleDatabase,\n bind,\n query,\n returning,\n source,\n type AnyEntityBinding,\n type DrizzleDatabaseService,\n type Visible,\n} from 'foldkit-remote-drizzle'\nimport {\n RemoteServer,\n RemoteServerError,\n type EntitySource,\n type MutationOutcome,\n type MutationSource,\n type QuerySource,\n} from 'foldkit-remote-server'\nimport type { CmsTables } from './tables.js'\n\nexport { pgTables, sqliteSchema, sqliteTables, type CmsTables } from './tables.js'\n\n/** The writes the CMS makes, as any Drizzle database for SQLite or Postgres offers them. */\ninterface Writes {\n insert(table: unknown): { values(values: object): PromiseLike<unknown> }\n update(table: unknown): {\n set(values: object): {\n where(condition: SQL | undefined): {\n returning(columns: Record<string, AnyColumn>): PromiseLike<ReadonlyArray<object>>\n }\n }\n }\n delete(table: unknown): { where(condition: SQL | undefined): PromiseLike<unknown> }\n}\n\n/**\n * A content type as the server holds it: its declaration, the binding of its\n * table, and the application's own handlers of its two publish mutations. What\n * publishing a post does stays the application's code.\n */\nexport interface ServedContent<P = any> {\n readonly type: Content<string, any, any, any>\n readonly binding: AnyEntityBinding\n readonly create: MutationSource<P, DrizzleDatabase>\n readonly update: MutationSource<P, DrizzleDatabase>\n}\n\n/** What an author may be refused, by `allow`. */\nexport type Asked =\n | 'save'\n | 'discard'\n | 'publish'\n | 'unpublish'\n | 'schedule'\n | 'unschedule'\n | 'archive'\n | 'unarchive'\n | 'restore'\n\n/**\n * Runs some work so that it happened entirely or did not. Drizzle's own\n * transactions differ by driver, so the application says which this is.\n */\nexport type Transaction = <A, E>(\n work: Effect.Effect<A, E, DrizzleDatabase>,\n) => Effect.Effect<A, E, DrizzleDatabase>\n\nconst statement = (database: DrizzleDatabaseService, text: string) =>\n Effect.promise(() => {\n const runs = database as unknown as {\n run?: (query: SQL) => unknown\n execute?: (query: SQL) => unknown\n }\n return Promise.resolve(\n runs.run !== undefined ? runs.run(sql.raw(text)) : runs.execute!(sql.raw(text)),\n )\n })\n\n// One connection holds one transaction. Without this, a write another request\n// makes between this one's `begin` and its `rollback` is rolled back with it,\n// after it was reported done.\nconst turns = new WeakMap<object, Semaphore.Semaphore>()\nconst turnOf = (database: object): Semaphore.Semaphore => {\n const held = turns.get(database)\n if (held !== undefined) return held\n const made = Semaphore.makeUnsafe(1)\n turns.set(database, made)\n return made\n}\n\nexport const Transaction = {\n /**\n * `begin`, the work, then `commit` or `rollback`, as statements. For a database\n * that is one connection, such as a SQLite file, which it takes turns at: the\n * operations of this package wait for one another. A write of your own on the\n * same connection does not, so make it inside `Transaction.statements` too.\n * Over a pool each statement may take a different connection: use `drizzle` there.\n */\n statements: (<A, E>(work: Effect.Effect<A, E, DrizzleDatabase>) =>\n Effect.gen(function* () {\n const database = yield* DrizzleDatabase\n return yield* turnOf(database).withPermit(\n statement(database, 'begin').pipe(\n Effect.andThen(work),\n Effect.onExit(exit => statement(database, Exit.isSuccess(exit) ? 'commit' : 'rollback')),\n ),\n )\n })) as Transaction,\n /**\n * Drizzle's `database.transaction`, with the work given the transaction as its\n * database. For a driver whose transactions are asynchronous: Postgres, libSQL.\n */\n drizzle: (<A, E>(work: Effect.Effect<A, E, DrizzleDatabase>) =>\n Effect.gen(function* () {\n const database = (yield* DrizzleDatabase) as unknown as {\n transaction: <T>(run: (inner: DrizzleDatabaseService) => Promise<T>) => Promise<T>\n }\n const failed = Symbol('failed')\n let exit: Exit.Exit<A, E> | undefined\n // A failure must leave the callback as a rejection, or Drizzle commits.\n yield* Effect.promise(() =>\n database\n .transaction(async inner => {\n exit = await Effect.runPromiseExit(\n work.pipe(Effect.provideService(DrizzleDatabase, inner)),\n )\n if (!Exit.isSuccess(exit)) throw failed\n })\n .catch(error => {\n if (error !== failed) throw error\n }),\n )\n return yield* exit!\n })) as Transaction,\n}\n\nexport interface CmsServerConfig<P> {\n readonly tables: CmsTables\n readonly content: ReadonlyArray<ServedContent<P>>\n /** How a publish is made whole: `Transaction.statements` or `Transaction.drizzle`. */\n readonly transaction: Transaction\n /** Who reads and writes unpublished work. Everyone else is a visitor. */\n readonly isAuthor: (principal: P) => boolean\n /**\n * Whether this author may make this transition. It is asked after the entry is\n * found and the transition is one its state offers. Default: any author may.\n */\n readonly allow?: (principal: P, transition: Asked, entry: EntryRow) => boolean\n /** The server's clock, passed in so a test can hold it. */\n readonly now?: () => Date\n /**\n * The most a draft may hold, as the characters of its JSON: its values and its\n * form's Model together. An author is trusted with their own content, not with\n * the database's disk. Default: one million.\n */\n readonly maxDraftSize?: number\n /** Who a principal is, for `createdBy` and `updatedBy`. */\n readonly nameOf?: (principal: P) => string | null\n}\n\n/** A row of `cms_entries`, as the server's rules see it. */\nexport interface EntryRow {\n readonly id: string\n readonly type: string\n readonly targetId: string | null\n readonly label: string\n readonly archivedAt: string | null\n readonly revision: number | null\n}\n\nconst refuse = (message: string) => new RemoteServerError({ message })\n\n/**\n * Whether a unique index on this column refused a write. Drivers say so in their\n * own words, and Drizzle wraps what they say, so every message down the chain of\n * causes is read. SQLite names the column; Postgres names the constraint, which\n * by default is made of the column's name.\n */\nconst refusedAsDuplicate = (defect: unknown, column: string): boolean => {\n const said: Array<string> = []\n for (let at = defect, depth = 0; at != null && depth < 5; depth++) {\n const { message, constraint, cause } = at as {\n readonly message?: unknown\n readonly constraint?: unknown\n readonly cause?: unknown\n }\n // Drizzle's own message quotes the statement, which names every column.\n if (!String(message).startsWith('Failed query'))\n said.push(String(message ?? at), String(constraint ?? ''))\n at = cause\n }\n const text = said.join(' ')\n const named = new RegExp(`(^|[^a-zA-Z0-9])${column.replace(/[^\\w]/g, '.')}([^a-zA-Z0-9]|$)`)\n return /unique|duplicate/i.test(text) && named.test(text)\n}\n\n/**\n * For a content table's binding: a visitor sees the rows whose `published`\n * column is set, and an author sees every row. Give it as the storage's\n * `visible` when the table is bound; `CmsServer.make` refuses a content type\n * that can be unpublished and whose binding shows every row to everyone.\n */\nexport const published =\n <P>(column: AnyColumn, isAuthor: (principal: P) => boolean): Visible =>\n principal =>\n isAuthor(principal as P) ? undefined : isNotNull(column)\n\nexport const CmsServer = {\n make: <P = unknown>(config: CmsServerConfig<P>) => {\n const { tables, isAuthor } = config\n const now = config.now ?? (() => new Date())\n const nameOf = config.nameOf ?? (() => null)\n const maxDraftSize = config.maxDraftSize ?? 1_000_000\n\n const byType = new Map(config.content.map(served => [served.type.name, served]))\n for (const { type, binding, create, update } of config.content) {\n for (const [handler, declared] of [\n [create, type.publish.create],\n [update, type.publish.update],\n ] as const)\n if (handler.mutation !== declared.name)\n throw new Error(\n `foldkit-cms-drizzle: content \"${type.name}\" publishes through \"${declared.name}\", but was given the handler of \"${handler.mutation}\"`,\n )\n if (type.roles.published === undefined) continue\n // Whether the rule is right is the application's to say: only it knows what a\n // principal is. That there is one at all is checked here.\n if (binding.visible === undefined)\n throw new Error(\n `foldkit-cms-drizzle: content \"${type.name}\" can be unpublished, but its binding shows every row to everyone; bind its table with \\`visible: published(column, isAuthor)\\``,\n )\n }\n\n // Unpublished work is an author's. To anyone else these tables are empty, on\n // every path they are read by.\n const authorsOnly: Visible = principal => (isAuthor(principal as P) ? undefined : sql`0 = 1`)\n const Db = bind(Cms.Entities, {\n Entry: {\n table: tables.entries,\n visible: authorsOnly,\n relations: {\n draft: { foreignKey: tables.drafts.id },\n revisions: {\n foreignKey: tables.revisions.entryId,\n orderBy: [{ column: tables.revisions.n, direction: 'desc' }],\n },\n },\n // Not a column: this server derives it, below, with its own clock.\n derived: { state: { supplied: true } },\n },\n Draft: { table: tables.drafts, visible: authorsOnly },\n Revision: { table: tables.revisions, visible: authorsOnly },\n })\n\n /** What is known of some entries, for their state: their drafts, and whether a visitor sees their rows. */\n const factsOf = (entries: ReadonlyArray<EntryRow>) =>\n Effect.gen(function* () {\n const database = yield* DrizzleDatabase\n const ids = entries.map(entry => entry.id)\n const drafts =\n ids.length === 0\n ? []\n : yield* Effect.promise(() =>\n Promise.resolve(\n database\n .select({\n id: tables.drafts.id,\n scheduledFor: tables.drafts.scheduledFor,\n scheduleError: tables.drafts.scheduleError,\n })\n .from(tables.drafts)\n .where(inArray(tables.drafts.id, ids)),\n ),\n )\n const draftOf = new Map(drafts.map(draft => [String(draft.id), draft]))\n\n // Per content type, which target rows exist and which a visitor would see.\n const rows = new Map<string, 'visible' | 'hidden'>()\n for (const [name, { type, binding }] of byType) {\n const targets = entries.flatMap(entry =>\n entry.type === name && entry.targetId !== null ? [entry.targetId] : [],\n )\n if (targets.length === 0) continue\n const id = binding.columns.id!\n const publishedColumn =\n type.roles.published === undefined\n ? undefined\n : binding.columns[type.roles.published.key]\n const found = yield* Effect.promise(() =>\n Promise.resolve(\n database\n .select({\n id,\n ...(publishedColumn === undefined ? {} : { shown: publishedColumn }),\n })\n .from(binding.table)\n .where(inArray(id, targets)),\n ),\n )\n for (const row of found)\n rows.set(\n `${name}:${String(row.id)}`,\n publishedColumn === undefined || row.shown !== null ? 'visible' : 'hidden',\n )\n }\n\n return new Map(\n entries.map((entry): readonly [string, Facts] => {\n const draft = draftOf.get(entry.id)\n return [\n entry.id,\n {\n archivedAt: entry.archivedAt,\n row:\n entry.targetId === null\n ? 'none'\n : (rows.get(`${entry.type}:${entry.targetId}`) ?? 'none'),\n draft:\n draft === undefined\n ? null\n : {\n scheduledFor: (draft.scheduledFor as string | null) ?? null,\n scheduleError: (draft.scheduleError as string | null) ?? null,\n },\n },\n ]\n }),\n )\n })\n\n const entryColumns = {\n id: tables.entries.id,\n type: tables.entries.type,\n targetId: tables.entries.targetId,\n label: tables.entries.label,\n archivedAt: tables.entries.archivedAt,\n revision: tables.entries.revision,\n }\n const findEntry = (id: string) =>\n Effect.gen(function* () {\n const database = yield* DrizzleDatabase\n const [row] = yield* Effect.promise(() =>\n Promise.resolve(\n database\n .select(entryColumns)\n .from(tables.entries)\n .where(eq(tables.entries.id, id))\n .limit(1),\n ),\n )\n return row as EntryRow | undefined\n })\n\n // An entry's `state` is not a column. The generated source reads the rest; the\n // state is derived from what is known of each entry, with this server's clock.\n const generated = source<P>(Db.Entry)\n const entries: EntitySource<P, DrizzleDatabase> = {\n ...generated,\n read: context =>\n Effect.gen(function* () {\n const wantsState = context.fields.includes('state')\n const fields = context.fields.filter(field => field !== 'state')\n const records = yield* generated.read({\n ...context,\n // The state needs these of each entry, asked for or not.\n fields: wantsState\n ? [...new Set([...fields, 'type', 'targetId', 'archivedAt'])]\n : fields,\n })\n if (!wantsState) return records\n const facts = yield* factsOf(\n records.map(record => ({\n id: record.id,\n type: String(record.values.type),\n targetId: (record.values.targetId as string | null) ?? null,\n label: '',\n archivedAt: (record.values.archivedAt as string | null) ?? null,\n revision: null,\n })),\n )\n const at = now()\n return records.map(record => ({\n id: record.id,\n values: {\n ...Object.fromEntries(\n Object.entries(record.values).filter(\n ([field]) => field === 'id' || context.fields.includes(field),\n ),\n ),\n state: Cms.state(facts.get(record.id)!, at),\n },\n }))\n }),\n }\n\n // The descriptor carries all three questions and the order; this says only\n // which table answers them. The audience boundary is unchanged: `visible`\n // on the binding is conjoined with the body, as for every source here.\n const worklist: QuerySource<P, DrizzleDatabase> = query<P, typeof Cms.Entries.Input.Type>(\n Cms.Entries,\n { entity: Db.Entry },\n )\n\n /** A mutation of this server: its principal and its database are fixed, its input is the descriptor's. */\n const operation = <Name extends string, Input, Output>(\n descriptor: MutationDescriptor<Name, Input, Output>,\n run: (context: {\n readonly input: Input\n readonly principal: P\n }) => Effect.Effect<MutationOutcome<Output>, RemoteServerError, DrizzleDatabase>,\n ): MutationSource<P, DrizzleDatabase> =>\n // Asked before anything is looked up: a visitor is not told which entries there are.\n // Then the whole of it is one transaction: a save that made an entry and\n // could not make its draft leaves neither.\n RemoteServer.mutation<P, DrizzleDatabase, Name, Input, Output>(descriptor, context =>\n isAuthor(context.principal)\n ? config.transaction(run(context))\n : Effect.fail(refuse('Only an author may change unpublished work')),\n )\n\n /**\n * A scheduled draft is published, when its time comes, as whoever scheduled it.\n * So changing it, replacing it or removing it is for someone who could have\n * scheduled it: otherwise an author who may not publish would publish through\n * someone who may.\n */\n const promised = (principal: P, entry: EntryRow) =>\n Effect.gen(function* () {\n const [draft] = yield* readRows(\n Db.Draft,\n { scheduledFor: tables.drafts.scheduledFor },\n entry.id,\n )\n if (draft?.scheduledFor == null) return\n if (config.allow?.(principal, 'schedule', entry) === false)\n return yield* refuse(\n 'This draft is scheduled, and only someone who may schedule it may change it',\n )\n })\n\n /** Whether the application lets this author do this, once the entry is known. */\n const asking = (principal: P, transition: Asked, entry: EntryRow | undefined) =>\n entry !== undefined && config.allow?.(principal, transition, entry) === false\n ? Effect.fail(refuse(`This author may not ${transition} this entry`))\n : Effect.void\n\n const draftFields = ['values', 'model', 'form', 'updatedAt', 'updatedBy', 'baseRevision']\n const draftPatch = returning(Db.Draft, draftFields)\n const entryPatch = returning(Db.Entry, [\n 'type',\n 'targetId',\n 'label',\n 'createdAt',\n 'archivedAt',\n 'revision',\n ])\n /**\n * The entry as the client should now hold it, its state with it: an operation\n * changes the state, and a patch without it would leave the old one on screen.\n */\n const entryPatches = (id: string) =>\n Effect.gen(function* () {\n const patches = entryPatch.patches(yield* readRows(Db.Entry, entryPatch.columns, id))\n const entry = yield* findEntry(id)\n if (entry === undefined) return patches\n const facts = (yield* factsOf([entry])).get(id)!\n return patches.map(patch => ({\n ...patch,\n values: { ...patch.values, state: Cms.state(facts, now()) },\n }))\n })\n const readRows = (binding: AnyEntityBinding, columns: Record<string, AnyColumn>, id: string) =>\n Effect.gen(function* () {\n const database = yield* DrizzleDatabase\n return yield* Effect.promise(() =>\n Promise.resolve(\n database.select(columns).from(binding.table).where(eq(binding.columns.id!, id)),\n ),\n )\n })\n\n const SaveDraft = operation(Cms.Operations.SaveDraft, ({ input, principal }) =>\n Effect.gen(function* () {\n if (!byType.has(input.type))\n return yield* refuse(`\"${input.type}\" is not a type of content this server knows`)\n const size = JSON.stringify([input.values, input.model]).length\n if (size > maxDraftSize)\n return yield* refuse(\n `This draft is too large to save: ${size} characters, and ${maxDraftSize} is the most`,\n )\n // An id nobody has is something new: the client named it, and this makes it.\n const existing = yield* findEntry(input.entry)\n if (existing !== undefined && existing.type !== input.type)\n return yield* refuse(`This entry is of \"${existing.type}\", not \"${input.type}\"`)\n if (existing?.archivedAt != null) return yield* refuse('An archived entry takes no draft')\n yield* asking(principal, 'save', existing)\n if (existing !== undefined) yield* promised(principal, existing)\n\n const database = (yield* DrizzleDatabase) as unknown as Writes\n const who = nameOf(principal)\n const id = existing?.id ?? input.entry\n const [held] = yield* readRows(Db.Draft, { updatedAt: tables.drafts.updatedAt }, id)\n const previous = held === undefined ? null : String(held.updatedAt)\n // Two saves in one millisecond must still be told apart by the next one.\n const at = now().toISOString()\n const updatedAt =\n previous !== null && at <= previous\n ? new Date(new Date(previous).getTime() + 1).toISOString()\n : at\n const written = {\n values: input.values,\n model: input.model,\n form: input.form,\n updatedAt,\n updatedBy: who,\n // A draft that changed is worth trying again when its time comes.\n scheduleError: null,\n }\n\n // The first draft of an entry has nothing to have been based on. Asked before\n // anything is written, so a refusal leaves no entry behind.\n if (previous === null && input.basedOn !== null)\n return yield* refuse('CmsConflict: this draft was discarded')\n if (existing === undefined) {\n yield* Effect.promise(() =>\n Promise.resolve(\n database.insert(tables.entries).values({\n id,\n type: input.type,\n targetId: null,\n label: input.label,\n createdBy: who,\n createdAt: at,\n archivedAt: null,\n revision: null,\n }),\n ),\n )\n }\n if (previous === null) {\n yield* Effect.promise(() =>\n Promise.resolve(\n database.insert(tables.drafts).values({ id, ...written, baseRevision: null }),\n ),\n )\n } else {\n // Compare and set: the save names what it was made from, and a newer one\n // on the server means someone else saved in between.\n const changed = yield* Effect.promise(() =>\n Promise.resolve(\n database\n .update(tables.drafts)\n .set(written)\n .where(\n and(eq(tables.drafts.id, id), eq(tables.drafts.updatedAt, input.basedOn ?? '')),\n )\n .returning({ id: tables.drafts.id }),\n ),\n )\n if (changed.length === 0)\n return yield* refuse('CmsConflict: this draft was saved by someone else since')\n }\n if (existing !== undefined && existing.label !== input.label) {\n yield* Effect.promise(() =>\n Promise.resolve(\n database\n .update(tables.entries)\n .set({ label: input.label })\n .where(eq(tables.entries.id, id))\n .returning({ id: tables.entries.id }),\n ),\n )\n }\n\n const entities = [\n ...(yield* entryPatches(id)),\n ...draftPatch.patches(yield* readRows(Db.Draft, draftPatch.columns, id)),\n ]\n return { output: { entry: id as never, updatedAt }, entities }\n }),\n )\n\n const DiscardDraft = operation(Cms.Operations.DiscardDraft, ({ input, principal }) =>\n Effect.gen(function* () {\n const entry = yield* findEntry(input.entry)\n if (entry === undefined) return yield* refuse('There is no such entry')\n yield* asking(principal, 'discard', entry)\n yield* promised(principal, entry)\n const database = (yield* DrizzleDatabase) as unknown as Writes\n yield* Effect.promise(() =>\n Promise.resolve(database.delete(tables.drafts).where(eq(tables.drafts.id, entry.id))),\n )\n const gone = [{ entity: 'CmsDraft', id: entry.id }]\n // Never published, and now with nothing entered: there is nothing left of it.\n if (entry.targetId !== null)\n return { output: {}, deleted: gone, entities: yield* entryPatches(entry.id) }\n yield* Effect.promise(() =>\n Promise.resolve(database.delete(tables.entries).where(eq(tables.entries.id, entry.id))),\n )\n return { output: {}, deleted: [...gone, { entity: 'CmsEntry', id: entry.id }] }\n }),\n )\n\n const done: Readonly<Record<Exclude<Asked, 'save' | 'discard'>, string>> = {\n publish: 'published',\n unpublish: 'unpublished',\n schedule: 'scheduled',\n unschedule: 'unscheduled',\n archive: 'archived',\n unarchive: 'unarchived',\n restore: 'restored',\n }\n /** The entry, if its state offers this transition now. */\n const offering = (id: string, transition: Exclude<Asked, 'save' | 'discard'>) =>\n Effect.gen(function* () {\n const entry = yield* findEntry(id)\n if (entry === undefined) return yield* refuse('There is no such entry')\n const served = byType.get(entry.type)\n if (served === undefined)\n return yield* refuse(`\"${entry.type}\" is not a type of content this server knows`)\n const facts = (yield* factsOf([entry])).get(entry.id)!\n if (!Cms.offers(facts, now(), served.type).includes(transition))\n return yield* refuse(`This entry cannot be ${done[transition]} as it stands`)\n return { entry, served, facts }\n })\n\n /** What the `published` role's column holds for \"now\". */\n const stamp = (column: AnyColumn, at: Date) => {\n const kind = column.dataType.split(' ')[0]\n return kind === 'string' ? at.toISOString() : kind === 'number' ? at.getTime() : at\n }\n\n const scheduledPatch = returning(Db.Draft, ['scheduledFor', 'scheduleError'])\n const revisionPatch = returning(Db.Revision, ['n', 'values', 'publishedAt', 'publishedBy'])\n\n type Found = Effect.Success<ReturnType<typeof offering>>\n\n /** The draft as the publish mutation would take it, or why it would not. */\n const readied = ({ entry, served, facts }: Found) =>\n Effect.gen(function* () {\n const [draft] = yield* readRows(Db.Draft, { values: tables.drafts.values }, entry.id)\n // A row that was deleted under its entry is made again.\n const creating = facts.row === 'none'\n const handler = creating ? served.create : served.update\n const value =\n creating || typeof draft?.values !== 'object' || draft.values === null\n ? draft?.values\n : { ...draft.values, id: entry.targetId }\n const decoded = yield* Schema.decodeUnknownEffect(handler.Input)(value).pipe(\n Effect.mapError(error => refuse(`This draft is not ready to publish: ${String(error)}`)),\n )\n return { draft, creating, handler, decoded }\n })\n\n /** A publish, for the operation that asks for one and for the schedule that comes due. */\n const publishing = (found: Found, principal: P, basedOn: number | null) =>\n Effect.gen(function* () {\n const { entry, served } = found\n const database = yield* DrizzleDatabase\n const held = entry.revision\n const conflict = refuse('CmsConflict: this entry was published by someone else since')\n if (held !== basedOn) return yield* conflict\n const { draft, creating, handler, decoded } = yield* readied(found)\n\n // A taken slug is refused by name, on its key. This is advice: two publishes\n // can both pass it, and then the unique index is the rule, below.\n const address = served.type.roles.slug\n const slug =\n address === undefined\n ? undefined\n : (decoded as Readonly<Record<string, unknown>> | null)?.[address.key]\n const taken = (key: string) => refuse(Cms.slugTaken.message(key, String(slug)))\n if (address !== undefined && typeof slug === 'string') {\n const column = served.binding.columns[address.key]!\n const id = served.binding.columns.id!\n const others = yield* Effect.promise(() =>\n Promise.resolve(\n database\n .select({ id })\n .from(served.binding.table)\n .where(and(eq(column, slug), creating ? undefined : ne(id, entry.targetId!)))\n .limit(1),\n ),\n )\n if (others.length > 0) return yield* taken(address.key)\n }\n\n return yield* Effect.gen(function* () {\n const writes = (yield* DrizzleDatabase) as unknown as Writes\n const n = (held ?? 0) + 1\n // Compare and set, first: of two publishes made from one revision, one\n // finds the number already moved, and nothing of it is written.\n const moved = yield* Effect.promise(() =>\n Promise.resolve(\n writes\n .update(tables.entries)\n .set({ revision: n })\n .where(\n and(\n eq(tables.entries.id, entry.id),\n held === null\n ? isNull(tables.entries.revision)\n : eq(tables.entries.revision, held),\n ),\n )\n .returning({ id: tables.entries.id }),\n ),\n )\n if (moved.length === 0) return yield* conflict\n const ran = yield* handler.run({ input: decoded, principal })\n const targetId = creating\n ? String((ran.output as { readonly id: unknown }).id)\n : entry.targetId!\n const at = now()\n const shown = served.type.roles.published\n if (shown !== undefined) {\n // Publishing shows the row. One already shown keeps its first date.\n const column = served.binding.columns[shown.key]!\n yield* Effect.promise(() =>\n Promise.resolve(\n writes\n .update(served.binding.table)\n .set({ [shown.key]: stamp(column, at) })\n .where(and(eq(served.binding.columns.id!, targetId), isNull(column)))\n .returning({ id: served.binding.columns.id! }),\n ),\n )\n }\n const revisionId = `${entry.id}:${n}`\n yield* Effect.promise(() =>\n Promise.resolve(\n writes.insert(tables.revisions).values({\n id: revisionId,\n entryId: entry.id,\n n,\n values: draft?.values,\n publishedAt: at.toISOString(),\n publishedBy: nameOf(principal),\n }),\n ),\n )\n yield* Effect.promise(() =>\n Promise.resolve(writes.delete(tables.drafts).where(eq(tables.drafts.id, entry.id))),\n )\n yield* Effect.promise(() =>\n Promise.resolve(\n writes\n .update(tables.entries)\n .set({ targetId })\n .where(eq(tables.entries.id, entry.id))\n .returning({ id: tables.entries.id }),\n ),\n )\n const content = returning(served.binding, shown === undefined ? [] : [shown.key])\n return {\n output: { entry: entry.id as never, targetId, revision: n },\n entities: [\n ...ran.entities,\n ...content.patches(yield* readRows(served.binding, content.columns, targetId)),\n ...(yield* entryPatches(entry.id)),\n ...revisionPatch.patches(\n yield* readRows(Db.Revision, revisionPatch.columns, revisionId),\n ),\n ],\n connections: ran.connections,\n deleted: [...ran.deleted, { entity: 'CmsDraft', id: entry.id }],\n }\n }).pipe(\n // The publish that lost the race: the index refused it, and it is the same\n // news as the check's. Any other defect is left as it was.\n Effect.catchDefect(defect =>\n address !== undefined &&\n refusedAsDuplicate(defect, served.binding.columns[address.key]!.name)\n ? Effect.fail(taken(address.key))\n : Effect.die(defect),\n ),\n )\n })\n\n const Publish = operation(Cms.Operations.Publish, ({ input, principal }) =>\n Effect.gen(function* () {\n const found = yield* offering(input.entry, 'publish')\n yield* asking(principal, 'publish', found.entry)\n return yield* publishing(found, principal, input.basedOn)\n }),\n )\n\n /** Writes to an entry's draft, and answers with both as the client should now hold them. */\n const drafted = (id: string, values: object) =>\n Effect.gen(function* () {\n const writes = (yield* DrizzleDatabase) as unknown as Writes\n yield* Effect.promise(() =>\n Promise.resolve(\n writes\n .update(tables.drafts)\n .set(values)\n .where(eq(tables.drafts.id, id))\n .returning({ id: tables.drafts.id }),\n ),\n )\n return {\n output: {},\n entities: [\n ...(yield* entryPatches(id)),\n ...scheduledPatch.patches(yield* readRows(Db.Draft, scheduledPatch.columns, id)),\n ],\n }\n })\n\n const Schedule = operation(Cms.Operations.Schedule, ({ input, principal }) =>\n Effect.gen(function* () {\n const found = yield* offering(input.entry, 'schedule')\n yield* asking(principal, 'schedule', found.entry)\n if (Number.isNaN(new Date(input.at).getTime()))\n return yield* refuse(`\"${input.at}\" is not a time`)\n // What cannot be published now is not promised for later.\n yield* readied(found)\n return yield* drafted(found.entry.id, {\n scheduledFor: new Date(input.at).toISOString(),\n scheduledBy: nameOf(principal),\n scheduleError: null,\n })\n }),\n )\n\n const Unschedule = operation(Cms.Operations.Unschedule, ({ input, principal }) =>\n Effect.gen(function* () {\n const found = yield* offering(input.entry, 'unschedule')\n yield* asking(principal, 'unschedule', found.entry)\n return yield* drafted(found.entry.id, {\n scheduledFor: null,\n scheduledBy: null,\n scheduleError: null,\n })\n }),\n )\n\n /** Archiving and its undoing: a fact about the entry, and for what can be hidden, the row. */\n const archiving = (transition: 'archive' | 'unarchive') =>\n operation(\n transition === 'archive' ? Cms.Operations.Archive : Cms.Operations.Unarchive,\n ({ input, principal }) =>\n Effect.gen(function* () {\n const { entry, served } = yield* offering(input.entry, transition)\n yield* asking(principal, transition, entry)\n const writes = (yield* DrizzleDatabase) as unknown as Writes\n yield* Effect.promise(() =>\n Promise.resolve(\n writes\n .update(tables.entries)\n .set({ archivedAt: transition === 'archive' ? now().toISOString() : null })\n .where(eq(tables.entries.id, entry.id))\n .returning({ id: tables.entries.id }),\n ),\n )\n const shown = served.type.roles.published\n if (transition === 'unarchive' || shown === undefined || entry.targetId === null)\n return { output: {}, entities: yield* entryPatches(entry.id) }\n // What is put away is not left on show. It comes back as unpublished work.\n yield* Effect.promise(() =>\n Promise.resolve(\n writes\n .update(served.binding.table)\n .set({ [shown.key]: null })\n .where(eq(served.binding.columns.id!, entry.targetId!))\n .returning({ id: served.binding.columns.id! }),\n ),\n )\n const content = returning(served.binding, [shown.key])\n return {\n output: {},\n entities: [\n ...content.patches(\n yield* readRows(served.binding, content.columns, entry.targetId),\n ),\n ...(yield* entryPatches(entry.id)),\n ],\n }\n }),\n )\n\n /**\n * A revision's value becomes the working copy, and nothing is published. It\n * replaces what the draft held, and takes back a promise made of that: what\n * was scheduled is not what is there now. The saved Model goes too, so an\n * editor fills its form from the values, key by key.\n */\n const Restore = operation(Cms.Operations.Restore, ({ input, principal }) =>\n Effect.gen(function* () {\n const { entry } = yield* offering(input.entry, 'restore')\n yield* asking(principal, 'restore', entry)\n yield* promised(principal, entry)\n const database = yield* DrizzleDatabase\n const [revision] = yield* Effect.promise(() =>\n Promise.resolve(\n database\n .select({ values: tables.revisions.values })\n .from(tables.revisions)\n .where(\n and(eq(tables.revisions.entryId, entry.id), eq(tables.revisions.n, input.revision)),\n )\n .limit(1),\n ),\n )\n if (revision === undefined)\n return yield* refuse(`This entry has no revision ${input.revision}`)\n\n const writes = database as unknown as Writes\n const [held] = yield* readRows(Db.Draft, { updatedAt: tables.drafts.updatedAt }, entry.id)\n const at = now().toISOString()\n const updatedAt =\n held !== undefined && at <= String(held.updatedAt)\n ? new Date(new Date(String(held.updatedAt)).getTime() + 1).toISOString()\n : at\n const restored = {\n values: revision.values,\n model: null,\n form: `restored@${input.revision}`,\n updatedAt,\n updatedBy: nameOf(principal),\n baseRevision: entry.revision,\n scheduledFor: null,\n scheduledBy: null,\n scheduleError: null,\n }\n yield* Effect.promise(() =>\n Promise.resolve(\n held === undefined\n ? writes.insert(tables.drafts).values({ id: entry.id, ...restored })\n : writes\n .update(tables.drafts)\n .set(restored)\n .where(eq(tables.drafts.id, entry.id))\n .returning({ id: tables.drafts.id }),\n ),\n )\n return {\n output: { updatedAt },\n entities: [\n ...(yield* entryPatches(entry.id)),\n ...draftPatch.patches(yield* readRows(Db.Draft, draftPatch.columns, entry.id)),\n ...scheduledPatch.patches(yield* readRows(Db.Draft, scheduledPatch.columns, entry.id)),\n ],\n }\n }),\n )\n\n /**\n * Publishes every draft whose time has come, each in its own transaction, as\n * whoever scheduled it: `as` says who a stored name is. One that fails stays\n * scheduled with the reason, and is left alone until its draft changes, so a\n * fault the author must fix is not tried again every minute. The package owns\n * no timer: a cron trigger, an interval or a queue calls this.\n */\n const due = (at: Date, options: { readonly as: (scheduledBy: string | null) => P }) =>\n Effect.gen(function* () {\n const database = yield* DrizzleDatabase\n const waiting = yield* Effect.promise(() =>\n Promise.resolve(\n database\n .select({ id: tables.drafts.id, scheduledBy: tables.drafts.scheduledBy })\n .from(tables.drafts)\n .where(\n and(\n isNotNull(tables.drafts.scheduledFor),\n lte(tables.drafts.scheduledFor, at.toISOString()),\n isNull(tables.drafts.scheduleError),\n // What is put away keeps its promise, and keeps it waiting.\n sql`${tables.drafts.id} in (select ${tables.entries.id} from ${tables.entries} where ${tables.entries.archivedAt} is null)`,\n ),\n ),\n ),\n )\n const outcomes: Array<{ readonly entry: string; readonly error: string | null }> = []\n for (const draft of waiting) {\n const id = String(draft.id)\n const principal = options.as((draft.scheduledBy as string | null) ?? null)\n const error = yield* config\n .transaction(\n Effect.gen(function* () {\n if (!isAuthor(principal))\n return yield* refuse('Whoever scheduled this is no longer an author')\n const found = yield* offering(id, 'publish')\n yield* asking(principal, 'publish', found.entry)\n yield* publishing(found, principal, found.entry.revision)\n return null\n }),\n )\n .pipe(\n Effect.catch(failure => Effect.succeed(failure.message)),\n Effect.catchDefect(defect => Effect.succeed(String(defect))),\n )\n // Recorded after the failed publish was undone, or it would be undone with it.\n if (error !== null) yield* config.transaction(drafted(id, { scheduleError: error }))\n outcomes.push({ entry: id, error })\n }\n return outcomes\n })\n\n const Unpublish = operation(Cms.Operations.Unpublish, ({ input, principal }) =>\n Effect.gen(function* () {\n const { entry, served } = yield* offering(input.entry, 'unpublish')\n yield* asking(principal, 'unpublish', entry)\n const shown = served.type.roles.published!\n const writes = (yield* DrizzleDatabase) as unknown as Writes\n yield* Effect.promise(() =>\n Promise.resolve(\n writes\n .update(served.binding.table)\n .set({ [shown.key]: null })\n .where(eq(served.binding.columns.id!, entry.targetId!))\n .returning({ id: served.binding.columns.id! }),\n ),\n )\n const content = returning(served.binding, [shown.key])\n return {\n output: {},\n entities: [\n ...content.patches(yield* readRows(served.binding, content.columns, entry.targetId!)),\n ...(yield* entryPatches(entry.id)),\n ],\n }\n }),\n )\n\n const sources: ReadonlyArray<EntitySource<P, DrizzleDatabase>> = [\n entries,\n source<P>(Db.Draft),\n source<P>(Db.Revision),\n ...config.content.map(served => source<P>(served.binding)),\n ]\n const mutations: ReadonlyArray<MutationSource<P, DrizzleDatabase>> = [\n SaveDraft,\n DiscardDraft,\n Publish,\n Unpublish,\n Schedule,\n Unschedule,\n archiving('archive'),\n archiving('unarchive'),\n Restore,\n ]\n\n // A content type with an address is found by it, behind the same boundary as\n // every other read of its table.\n // The descriptor carries what the query means, so this registers the source\n // and says no more: the address column and the order come from the body,\n // through the binding that knows which column holds which field.\n const bySlug = config.content.flatMap(({ type, binding }) =>\n type.roles.slug === undefined\n ? []\n : [\n query<P, { readonly slug: string }>(Cms.bySlug(type), {\n entity: binding,\n }) as QuerySource<P, DrizzleDatabase>,\n ],\n )\n\n return {\n /**\n * The sources of the content types and of the CMS's own Entities, each behind\n * the audience boundary. Register these and not `source(binding)` of a content\n * table: that is how the boundary cannot be forgotten.\n */\n sources,\n queries: [worklist, ...bySlug] as ReadonlyArray<QuerySource<P, DrizzleDatabase>>,\n mutations,\n due,\n /** The bindings of `Entry`, `Draft` and `Revision`, for a handler that returns patches of them. */\n bindings: Db,\n }\n },\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["text","integer","pgText","pgInteger"],"sources":["../src/tables.ts","../src/index.ts"],"sourcesContent":["/**\n * The three tables the CMS keeps beside the application's own. Drizzle tables\n * are made per dialect, so there is one definition for each; they have the same\n * columns. An application adds them to its schema and its migrations as it does\n * any table, and may define its own instead, so long as the columns are these.\n */\nimport { integer as pgInteger, jsonb, pgTable, text as pgText } from 'drizzle-orm/pg-core'\nimport { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'\n\n/**\n * - `cms_entries`: a piece of content from first keystroke to archive. It exists\n * before its content row does.\n * - `cms_drafts`: an entry's one working copy; its id is the entry's.\n * - `cms_revisions`: published values, append-only.\n */\nexport const sqliteTables = () => ({\n entries: sqliteTable('cms_entries', {\n id: text('id').primaryKey(),\n type: text('type').notNull(),\n targetId: text('target_id'),\n label: text('label').notNull(),\n createdBy: text('created_by'),\n createdAt: text('created_at').notNull(),\n archivedAt: text('archived_at'),\n revision: integer('revision'),\n }),\n drafts: sqliteTable('cms_drafts', {\n id: text('id').primaryKey(),\n values: text('values', { mode: 'json' }),\n model: text('model', { mode: 'json' }),\n form: text('form').notNull(),\n updatedAt: text('updated_at').notNull(),\n updatedBy: text('updated_by'),\n baseRevision: integer('base_revision'),\n scheduledFor: text('scheduled_for'),\n scheduledBy: text('scheduled_by'),\n scheduleError: text('schedule_error'),\n }),\n revisions: sqliteTable('cms_revisions', {\n id: text('id').primaryKey(),\n entryId: text('entry_id').notNull(),\n n: integer('n').notNull(),\n values: text('values', { mode: 'json' }),\n publishedAt: text('published_at').notNull(),\n publishedBy: text('published_by'),\n }),\n})\n\nexport const pgTables = () => ({\n entries: pgTable('cms_entries', {\n id: pgText('id').primaryKey(),\n type: pgText('type').notNull(),\n targetId: pgText('target_id'),\n label: pgText('label').notNull(),\n createdBy: pgText('created_by'),\n createdAt: pgText('created_at').notNull(),\n archivedAt: pgText('archived_at'),\n revision: pgInteger('revision'),\n }),\n drafts: pgTable('cms_drafts', {\n id: pgText('id').primaryKey(),\n values: jsonb('values'),\n model: jsonb('model'),\n form: pgText('form').notNull(),\n updatedAt: pgText('updated_at').notNull(),\n updatedBy: pgText('updated_by'),\n baseRevision: pgInteger('base_revision'),\n scheduledFor: pgText('scheduled_for'),\n scheduledBy: pgText('scheduled_by'),\n scheduleError: pgText('schedule_error'),\n }),\n revisions: pgTable('cms_revisions', {\n id: pgText('id').primaryKey(),\n entryId: pgText('entry_id').notNull(),\n n: pgInteger('n').notNull(),\n values: jsonb('values'),\n publishedAt: pgText('published_at').notNull(),\n publishedBy: pgText('published_by'),\n }),\n})\n\n/** The `create table` statements for SQLite, for a test or a first migration. */\nexport const sqliteSchema = `\ncreate table if not exists cms_entries (\n id text primary key, type text not null, target_id text, label text not null,\n created_by text, created_at text not null, archived_at text, revision integer\n);\ncreate table if not exists cms_drafts (\n id text primary key, \"values\" text, model text, form text not null,\n updated_at text not null, updated_by text, base_revision integer,\n scheduled_for text, scheduled_by text, schedule_error text\n);\ncreate table if not exists cms_revisions (\n id text primary key, entry_id text not null, n integer not null, \"values\" text,\n published_at text not null, published_by text\n);\n`\n\nexport type CmsTables = ReturnType<typeof sqliteTables> | ReturnType<typeof pgTables>\n","/**\n * `foldkit-cms-drizzle` — the server half of `foldkit-cms`.\n *\n * It keeps an author's unpublished work in three tables beside the\n * application's own, and it is where the audience boundary is enforced: a\n * principal that is not an author is refused entries, drafts and revisions\n * outright, and sees of the content only the rows its `published` role shows.\n * Both are a binding's `visible`, so they hold on every path a table is read by.\n *\n * Publishing runs the application's own mutation with the draft's value, inside\n * a transaction that also appends the revision and removes the draft, so a\n * publish happened entirely or did not happen.\n */\nimport {\n and,\n eq,\n inArray,\n isNotNull,\n isNull,\n lte,\n ne,\n sql,\n type AnyColumn,\n type SQL,\n} from 'drizzle-orm'\nimport { Effect, Exit, Schema, Semaphore } from 'effect'\nimport { Cms, type Content, type Facts, type Transition } from 'foldkit-cms'\nimport type { MutationDescriptor } from 'foldkit-remote'\nimport {\n DrizzleDatabase,\n drizzleWrites,\n bind,\n query,\n returning,\n source,\n type AnyEntityBinding,\n type DrizzleDatabaseService,\n type Visible,\n} from 'foldkit-remote-drizzle'\nimport {\n RemoteServer,\n RemoteServerError,\n type EntitySource,\n type MutationOutcome,\n type MutationSource,\n type QuerySource,\n} from 'foldkit-remote-server'\nimport type { CmsTables } from './tables.js'\n\nexport { pgTables, sqliteSchema, sqliteTables, type CmsTables } from './tables.js'\n\n/**\n * A content type as the server holds it: its declaration, the binding of its\n * table, and the application's own handlers of its two publish mutations. What\n * publishing a post does stays the application's code.\n */\nexport interface ServedContent<P = any> {\n readonly type: Content<string, any, any, any>\n readonly binding: AnyEntityBinding\n readonly create: MutationSource<P, DrizzleDatabase>\n readonly update: MutationSource<P, DrizzleDatabase>\n}\n\n/**\n * Runs some work so that it happened entirely or did not. Drizzle's own\n * transactions differ by driver, so the application says which this is.\n */\nexport type Transaction = <A, E>(\n work: Effect.Effect<A, E, DrizzleDatabase>,\n) => Effect.Effect<A, E, DrizzleDatabase>\n\nconst statement = (database: DrizzleDatabaseService, text: string) =>\n Effect.promise(() => {\n const runs = database as unknown as {\n run?: (query: SQL) => unknown\n execute?: (query: SQL) => unknown\n }\n return Promise.resolve(\n runs.run !== undefined ? runs.run(sql.raw(text)) : runs.execute!(sql.raw(text)),\n )\n })\n\n// One connection holds one transaction. Without this, a write another request\n// makes between this one's `begin` and its `rollback` is rolled back with it,\n// after it was reported done.\nconst turns = new WeakMap<object, Semaphore.Semaphore>()\nconst turnOf = (database: object): Semaphore.Semaphore => {\n const held = turns.get(database)\n if (held !== undefined) return held\n const made = Semaphore.makeUnsafe(1)\n turns.set(database, made)\n return made\n}\n\nexport const Transaction = {\n /**\n * `begin`, the work, then `commit` or `rollback`, as statements. For a database\n * that is one connection, such as a SQLite file, which it takes turns at: the\n * operations of this package wait for one another. A write of your own on the\n * same connection does not, so make it inside `Transaction.statements` too.\n * Over a pool each statement may take a different connection: use `drizzle` there.\n */\n statements: (<A, E>(work: Effect.Effect<A, E, DrizzleDatabase>) =>\n Effect.gen(function* () {\n const database = yield* DrizzleDatabase\n return yield* turnOf(database).withPermit(\n statement(database, 'begin').pipe(\n Effect.andThen(work),\n Effect.onExit(exit => statement(database, Exit.isSuccess(exit) ? 'commit' : 'rollback')),\n ),\n )\n })) as Transaction,\n /**\n * Drizzle's `database.transaction`, with the work given the transaction as its\n * database. For a driver whose transactions are asynchronous: Postgres, libSQL.\n */\n drizzle: (<A, E>(work: Effect.Effect<A, E, DrizzleDatabase>) =>\n Effect.gen(function* () {\n const database = (yield* DrizzleDatabase) as unknown as {\n transaction: <T>(run: (inner: DrizzleDatabaseService) => Promise<T>) => Promise<T>\n }\n const failed = Symbol('failed')\n let exit: Exit.Exit<A, E> | undefined\n // A failure must leave the callback as a rejection, or Drizzle commits.\n yield* Effect.promise(() =>\n database\n .transaction(async inner => {\n exit = await Effect.runPromiseExit(\n work.pipe(Effect.provideService(DrizzleDatabase, inner)),\n )\n if (!Exit.isSuccess(exit)) throw failed\n })\n .catch(error => {\n if (error !== failed) throw error\n }),\n )\n return yield* exit!\n })) as Transaction,\n}\n\nexport interface CmsServerConfig<P> {\n readonly tables: CmsTables\n readonly content: ReadonlyArray<ServedContent<P>>\n /** How a publish is made whole: `Transaction.statements` or `Transaction.drizzle`. */\n readonly transaction: Transaction\n /** Who reads and writes unpublished work. Everyone else is a visitor. */\n readonly isAuthor: (principal: P) => boolean\n /**\n * Whether this author may make this transition. It is asked after the entry is\n * found and the transition is one its state offers. Default: any author may.\n */\n readonly allow?: (principal: P, transition: Transition, entry: EntryRow) => boolean\n /** The server's clock, passed in so a test can hold it. */\n readonly now?: () => Date\n /**\n * The most a draft may hold, as the characters of its JSON: its values and its\n * form's Model together. An author is trusted with their own content, not with\n * the database's disk. Default: one million.\n */\n readonly maxDraftSize?: number\n /** Who a principal is, for `createdBy` and `updatedBy`. */\n readonly nameOf?: (principal: P) => string | null\n}\n\n/** A row of `cms_entries`, as the server's rules see it. */\nexport interface EntryRow {\n readonly id: string\n readonly type: string\n readonly targetId: string | null\n readonly label: string\n readonly archivedAt: string | null\n readonly revision: number | null\n}\n\n/**\n * One import: content published already, such as a site's first pages or what\n * another CMS held. `as` is whom it is imported as (an editor, so the create\n * handler writes the row); a runner that imports many fixes `as` once and\n * varies the rest. `at` is when it was published (default now); `entry`\n * names the entry (default a new id) — name it where another import points\n * at it, and import what is pointed at first.\n */\nexport interface ImportItem<P> {\n readonly type: string\n readonly values: unknown\n readonly as: P\n readonly at?: Date | undefined\n readonly entry?: string | undefined\n}\n\nconst refuse = (message: string) => new RemoteServerError({ message })\n\n/**\n * Whether a unique index on this column refused a write. Drivers say so in their\n * own words, and Drizzle wraps what they say, so every message down the chain of\n * causes is read. SQLite names the column; Postgres names the constraint, which\n * by default is made of the column's name.\n */\nconst refusedAsDuplicate = (defect: unknown, column: string): boolean => {\n const said: Array<string> = []\n for (let at = defect, depth = 0; at != null && depth < 5; depth++) {\n const { message, constraint, cause } = at as {\n readonly message?: unknown\n readonly constraint?: unknown\n readonly cause?: unknown\n }\n // Drizzle's own message quotes the statement, which names every column.\n if (!String(message).startsWith('Failed query'))\n said.push(String(message ?? at), String(constraint ?? ''))\n at = cause\n }\n const text = said.join(' ')\n const named = new RegExp(`(^|[^a-zA-Z0-9])${column.replace(/[^\\w]/g, '.')}([^a-zA-Z0-9]|$)`)\n return /unique|duplicate/i.test(text) && named.test(text)\n}\n\n/**\n * For a content table's binding: a visitor sees the rows whose `published`\n * column is set, and an author sees every row. Give it as the storage's\n * `visible` when the table is bound; `CmsServer.make` refuses a content type\n * that can be unpublished and whose binding shows every row to everyone.\n */\nexport const published =\n <P>(column: AnyColumn, isAuthor: (principal: P) => boolean): Visible =>\n principal =>\n isAuthor(principal as P) ? undefined : isNotNull(column)\n\nexport const CmsServer = {\n make: <P = unknown>(config: CmsServerConfig<P>) => {\n const { tables, isAuthor } = config\n const now = config.now ?? (() => new Date())\n const nameOf = config.nameOf ?? (() => null)\n const maxDraftSize = config.maxDraftSize ?? 1_000_000\n\n const byType = new Map(config.content.map(served => [served.type.name, served]))\n for (const { type, binding, create, update } of config.content) {\n for (const [handler, declared] of [\n [create, type.publish.create],\n [update, type.publish.update],\n ] as const)\n if (handler.mutation !== declared.name)\n throw new Error(\n `foldkit-cms-drizzle: content \"${type.name}\" publishes through \"${declared.name}\", but was given the handler of \"${handler.mutation}\"`,\n )\n if (type.roles.published === undefined) continue\n // Whether the rule is right is the application's to say: only it knows what a\n // principal is. That there is one at all is checked here.\n if (binding.visible === undefined)\n throw new Error(\n `foldkit-cms-drizzle: content \"${type.name}\" can be unpublished, but its binding shows every row to everyone; bind its table with \\`visible: published(column, isAuthor)\\``,\n )\n }\n\n // Unpublished work is an author's. To anyone else these tables are empty, on\n // every path they are read by.\n const authorsOnly: Visible = principal => (isAuthor(principal as P) ? undefined : sql`0 = 1`)\n const Db = bind(Cms.Entities, {\n Entry: {\n table: tables.entries,\n visible: authorsOnly,\n relations: {\n draft: { foreignKey: tables.drafts.id },\n revisions: {\n foreignKey: tables.revisions.entryId,\n orderBy: [{ column: tables.revisions.n, direction: 'desc' }],\n },\n },\n // Not a column: this server derives it, below, with its own clock.\n derived: { state: { supplied: true }, may: { supplied: true } },\n },\n Draft: { table: tables.drafts, visible: authorsOnly },\n Revision: { table: tables.revisions, visible: authorsOnly },\n })\n\n /** What is known of some entries, for their state: their drafts, and whether a visitor sees their rows. */\n const factsOf = (entries: ReadonlyArray<EntryRow>) =>\n Effect.gen(function* () {\n const database = yield* DrizzleDatabase\n const ids = entries.map(entry => entry.id)\n const drafts =\n ids.length === 0\n ? []\n : yield* Effect.promise(() =>\n Promise.resolve(\n database\n .select({\n id: tables.drafts.id,\n scheduledFor: tables.drafts.scheduledFor,\n scheduleError: tables.drafts.scheduleError,\n })\n .from(tables.drafts)\n .where(inArray(tables.drafts.id, ids)),\n ),\n )\n const draftOf = new Map(drafts.map(draft => [String(draft.id), draft]))\n\n // Per content type, which target rows exist and which a visitor would see.\n const rows = new Map<string, 'visible' | 'hidden'>()\n for (const [name, { type, binding }] of byType) {\n const targets = entries.flatMap(entry =>\n entry.type === name && entry.targetId !== null ? [entry.targetId] : [],\n )\n if (targets.length === 0) continue\n const id = binding.columns.id!\n const publishedColumn =\n type.roles.published === undefined\n ? undefined\n : binding.columns[type.roles.published.key]\n const found = yield* Effect.promise(() =>\n Promise.resolve(\n database\n .select({\n id,\n ...(publishedColumn === undefined ? {} : { shown: publishedColumn }),\n })\n .from(binding.table)\n .where(inArray(id, targets)),\n ),\n )\n for (const row of found)\n rows.set(\n `${name}:${String(row.id)}`,\n publishedColumn === undefined || row.shown !== null ? 'visible' : 'hidden',\n )\n }\n\n return new Map(\n entries.map((entry): readonly [string, Facts] => {\n const draft = draftOf.get(entry.id)\n return [\n entry.id,\n {\n archivedAt: entry.archivedAt,\n row:\n entry.targetId === null\n ? 'none'\n : (rows.get(`${entry.type}:${entry.targetId}`) ?? 'none'),\n draft:\n draft === undefined\n ? null\n : {\n scheduledFor: (draft.scheduledFor as string | null) ?? null,\n scheduleError: (draft.scheduleError as string | null) ?? null,\n },\n },\n ]\n }),\n )\n })\n\n const entryColumns = {\n id: tables.entries.id,\n type: tables.entries.type,\n targetId: tables.entries.targetId,\n label: tables.entries.label,\n archivedAt: tables.entries.archivedAt,\n revision: tables.entries.revision,\n }\n const findEntry = (id: string) =>\n Effect.gen(function* () {\n const database = yield* DrizzleDatabase\n const [row] = yield* Effect.promise(() =>\n Promise.resolve(\n database\n .select(entryColumns)\n .from(tables.entries)\n .where(eq(tables.entries.id, id))\n .limit(1),\n ),\n )\n return row as EntryRow | undefined\n })\n\n // An entry's `state` and `may` are not columns. The generated source reads the\n // rest; the state is derived from what is known of each entry, with this server's\n // clock, and `may` is what `allow` lets the reader ask of it.\n const generated = source<P>(Db.Entry)\n const entries: EntitySource<P, DrizzleDatabase> = {\n ...generated,\n read: context =>\n Effect.gen(function* () {\n const wantsState = context.fields.includes('state')\n const wantsMay = context.fields.includes('may')\n const fields = context.fields.filter(field => field !== 'state' && field !== 'may')\n const records = yield* generated.read({\n ...context,\n // Both need these of each entry, asked for or not; `allow` is given the row.\n fields:\n wantsState || wantsMay\n ? [\n ...new Set([\n ...fields,\n 'type',\n 'targetId',\n 'archivedAt',\n ...(wantsMay ? ['label', 'revision'] : []),\n ]),\n ]\n : fields,\n })\n if (!wantsState && !wantsMay) return records\n const rows = records.map((record): EntryRow => ({\n id: record.id,\n type: String(record.values.type),\n targetId: (record.values.targetId as string | null) ?? null,\n label: String(record.values.label ?? ''),\n archivedAt: (record.values.archivedAt as string | null) ?? null,\n revision: (record.values.revision as number | null) ?? null,\n }))\n const facts = yield* factsOf(rows)\n const at = now()\n return records.map((record, index) => {\n const row = rows[index]!\n const known = facts.get(record.id)!\n return {\n id: record.id,\n values: {\n ...Object.fromEntries(\n Object.entries(record.values).filter(\n ([field]) => field === 'id' || context.fields.includes(field),\n ),\n ),\n ...(wantsState ? { state: Cms.state(known, at) } : {}),\n // What `allow` lets this reader ask of this entry, whatever its state\n // offers now: the state says that, and a draft saved a moment later\n // changes it without asking the server again.\n ...(wantsMay\n ? {\n may: Cms.transitions.filter(\n transition => config.allow?.(context.principal, transition, row) !== false,\n ),\n }\n : {}),\n },\n }\n })\n }),\n }\n\n // The descriptor carries all three questions and the order; this says only\n // which table answers them. The audience boundary is unchanged: `visible`\n // on the binding is conjoined with the body, as for every source here.\n const worklist: QuerySource<P, DrizzleDatabase> = query(Cms.Entries, { entity: Db.Entry })\n\n /** A mutation of this server: its principal and its database are fixed, its input is the descriptor's. */\n const operation = <Name extends string, Input, Output>(\n descriptor: MutationDescriptor<Name, Input, Output>,\n run: (context: {\n readonly input: Input\n readonly principal: P\n }) => Effect.Effect<MutationOutcome<Output>, RemoteServerError, DrizzleDatabase>,\n ): MutationSource<P, DrizzleDatabase> =>\n // Asked before anything is looked up: a visitor is not told which entries there are.\n // Then the whole of it is one transaction: a save that made an entry and\n // could not make its draft leaves neither.\n RemoteServer.mutation<P, DrizzleDatabase, Name, Input, Output>(descriptor, context =>\n isAuthor(context.principal)\n ? config.transaction(run(context))\n : Effect.fail(refuse('Only an author may change unpublished work')),\n )\n\n /**\n * A scheduled draft is published, when its time comes, as whoever scheduled it.\n * So changing it, replacing it or removing it is for someone who could have\n * scheduled it: otherwise an author who may not publish would publish through\n * someone who may.\n */\n const promised = (principal: P, entry: EntryRow) =>\n Effect.gen(function* () {\n const [draft] = yield* readRows(\n Db.Draft,\n { scheduledFor: tables.drafts.scheduledFor },\n entry.id,\n )\n if (draft?.scheduledFor == null) return\n if (config.allow?.(principal, 'schedule', entry) === false)\n return yield* refuse(\n 'This draft is scheduled, and only someone who may schedule it may change it',\n )\n })\n\n /** Whether the application lets this author do this, once the entry is known. */\n const asking = (principal: P, transition: Transition, entry: EntryRow | undefined) =>\n entry !== undefined && config.allow?.(principal, transition, entry) === false\n ? Effect.fail(refuse(`This author may not ${transition} this entry`))\n : Effect.void\n\n const draftFields = ['values', 'model', 'form', 'updatedAt', 'updatedBy', 'baseRevision']\n const draftPatch = returning(Db.Draft, draftFields)\n const entryPatch = returning(Db.Entry, [\n 'type',\n 'targetId',\n 'label',\n 'createdAt',\n 'archivedAt',\n 'revision',\n ])\n /**\n * The entry as the client should now hold it, its state with it: an operation\n * changes the state, and a patch without it would leave the old one on screen.\n */\n const entryPatches = (id: string) =>\n Effect.gen(function* () {\n const patches = entryPatch.patches(yield* readRows(Db.Entry, entryPatch.columns, id))\n const entry = yield* findEntry(id)\n if (entry === undefined) return patches\n const facts = (yield* factsOf([entry])).get(id)!\n return patches.map(patch => ({\n ...patch,\n values: { ...patch.values, state: Cms.state(facts, now()) },\n }))\n })\n const readRows = (binding: AnyEntityBinding, columns: Record<string, AnyColumn>, id: string) =>\n Effect.gen(function* () {\n const database = yield* DrizzleDatabase\n return yield* Effect.promise(() =>\n Promise.resolve(\n database.select(columns).from(binding.table).where(eq(binding.columns.id!, id)),\n ),\n )\n })\n\n const SaveDraft = operation(Cms.Operations.SaveDraft, ({ input, principal }) =>\n Effect.gen(function* () {\n if (!byType.has(input.type))\n return yield* refuse(`\"${input.type}\" is not a type of content this server knows`)\n const size = JSON.stringify([input.values, input.model]).length\n if (size > maxDraftSize)\n return yield* refuse(\n `This draft is too large to save: ${size} characters, and ${maxDraftSize} is the most`,\n )\n // An id nobody has is something new: the client named it, and this makes it.\n const existing = yield* findEntry(input.entry)\n if (existing !== undefined && existing.type !== input.type)\n return yield* refuse(`This entry is of \"${existing.type}\", not \"${input.type}\"`)\n if (existing?.archivedAt != null) return yield* refuse('An archived entry takes no draft')\n yield* asking(principal, 'save', existing)\n if (existing !== undefined) yield* promised(principal, existing)\n\n const database = yield* drizzleWrites\n const who = nameOf(principal)\n const id = existing?.id ?? input.entry\n const [held] = yield* readRows(Db.Draft, { updatedAt: tables.drafts.updatedAt }, id)\n const previous = held === undefined ? null : String(held.updatedAt)\n // Two saves in one millisecond must still be told apart by the next one.\n const at = now().toISOString()\n const updatedAt =\n previous !== null && at <= previous\n ? new Date(new Date(previous).getTime() + 1).toISOString()\n : at\n const written = {\n values: input.values,\n model: input.model,\n form: input.form,\n updatedAt,\n updatedBy: who,\n // A draft that changed is worth trying again when its time comes.\n scheduleError: null,\n }\n\n // The first draft of an entry has nothing to have been based on. Asked before\n // anything is written, so a refusal leaves no entry behind.\n if (previous === null && input.basedOn !== null)\n return yield* refuse('CmsConflict: this draft was discarded')\n if (existing === undefined) {\n yield* Effect.promise(() =>\n Promise.resolve(\n database.insert(tables.entries).values({\n id,\n type: input.type,\n targetId: null,\n label: input.label,\n createdBy: who,\n createdAt: at,\n archivedAt: null,\n revision: null,\n }),\n ),\n )\n }\n if (previous === null) {\n yield* Effect.promise(() =>\n Promise.resolve(\n database.insert(tables.drafts).values({ id, ...written, baseRevision: null }),\n ),\n )\n } else {\n // Compare and set: the save names what it was made from, and a newer one\n // on the server means someone else saved in between.\n const changed = yield* Effect.promise(() =>\n Promise.resolve(\n database\n .update(tables.drafts)\n .set(written)\n .where(\n and(eq(tables.drafts.id, id), eq(tables.drafts.updatedAt, input.basedOn ?? '')),\n )\n .returning({ id: tables.drafts.id }),\n ),\n )\n if (changed.length === 0)\n return yield* refuse('CmsConflict: this draft was saved by someone else since')\n }\n if (existing !== undefined && existing.label !== input.label) {\n yield* Effect.promise(() =>\n Promise.resolve(\n database\n .update(tables.entries)\n .set({ label: input.label })\n .where(eq(tables.entries.id, id))\n .returning({ id: tables.entries.id }),\n ),\n )\n }\n\n const entities = [\n ...(yield* entryPatches(id)),\n ...draftPatch.patches(yield* readRows(Db.Draft, draftPatch.columns, id)),\n ]\n return { output: { entry: id as never, updatedAt }, entities }\n }),\n )\n\n const DiscardDraft = operation(Cms.Operations.DiscardDraft, ({ input, principal }) =>\n Effect.gen(function* () {\n const entry = yield* findEntry(input.entry)\n if (entry === undefined) return yield* refuse('There is no such entry')\n yield* asking(principal, 'discard', entry)\n yield* promised(principal, entry)\n const database = yield* drizzleWrites\n yield* Effect.promise(() =>\n Promise.resolve(database.delete(tables.drafts).where(eq(tables.drafts.id, entry.id))),\n )\n const gone = [{ entity: 'CmsDraft', id: entry.id }]\n // Never published, and now with nothing entered: there is nothing left of it.\n if (entry.targetId !== null)\n return { output: {}, deleted: gone, entities: yield* entryPatches(entry.id) }\n yield* Effect.promise(() =>\n Promise.resolve(database.delete(tables.entries).where(eq(tables.entries.id, entry.id))),\n )\n return { output: {}, deleted: [...gone, { entity: 'CmsEntry', id: entry.id }] }\n }),\n )\n\n const done: Readonly<Record<Exclude<Transition, 'save' | 'discard'>, string>> = {\n publish: 'published',\n unpublish: 'unpublished',\n schedule: 'scheduled',\n unschedule: 'unscheduled',\n archive: 'archived',\n unarchive: 'unarchived',\n restore: 'restored',\n }\n /** The entry, if its state offers this transition now. */\n const offering = (id: string, transition: Exclude<Transition, 'save' | 'discard'>) =>\n Effect.gen(function* () {\n const entry = yield* findEntry(id)\n if (entry === undefined) return yield* refuse('There is no such entry')\n const served = byType.get(entry.type)\n if (served === undefined)\n return yield* refuse(`\"${entry.type}\" is not a type of content this server knows`)\n const facts = (yield* factsOf([entry])).get(entry.id)!\n if (!Cms.offers(facts, now(), served.type).includes(transition))\n return yield* refuse(`This entry cannot be ${done[transition]} as it stands`)\n return { entry, served, facts }\n })\n\n /** What the `published` role's column holds for \"now\". */\n const stamp = (column: AnyColumn, at: Date) => {\n const kind = column.dataType.split(' ')[0]\n return kind === 'string' ? at.toISOString() : kind === 'number' ? at.getTime() : at\n }\n\n const scheduledPatch = returning(Db.Draft, ['scheduledFor', 'scheduleError'])\n const revisionPatch = returning(Db.Revision, ['n', 'values', 'publishedAt', 'publishedBy'])\n\n type Found = Effect.Success<ReturnType<typeof offering>>\n\n /** The draft as the publish mutation would take it, or why it would not. */\n const readied = ({ entry, served, facts }: Found) =>\n Effect.gen(function* () {\n const [draft] = yield* readRows(Db.Draft, { values: tables.drafts.values }, entry.id)\n // A row that was deleted under its entry is made again.\n const creating = facts.row === 'none'\n const handler = creating ? served.create : served.update\n const value =\n creating || typeof draft?.values !== 'object' || draft.values === null\n ? draft?.values\n : { ...draft.values, id: entry.targetId }\n const decoded = yield* Schema.decodeUnknownEffect(handler.Input)(value).pipe(\n Effect.mapError(error => refuse(`This draft is not ready to publish: ${String(error)}`)),\n )\n return { draft, creating, handler, decoded }\n })\n\n /** A publish, for the operation that asks for one, the schedule that comes due, and an import. */\n const publishing = (found: Found, principal: P, basedOn: number | null, at: Date = now()) =>\n Effect.gen(function* () {\n const { entry, served } = found\n const database = yield* DrizzleDatabase\n const held = entry.revision\n const conflict = refuse('CmsConflict: this entry was published by someone else since')\n if (held !== basedOn) return yield* conflict\n const { draft, creating, handler, decoded } = yield* readied(found)\n\n // A taken slug is refused by name, on its key. This is advice: two publishes\n // can both pass it, and then the unique index is the rule, below.\n const address = served.type.roles.slug\n const slug =\n address === undefined\n ? undefined\n : (decoded as Readonly<Record<string, unknown>> | null)?.[address.key]\n const taken = (key: string) => refuse(Cms.slugTaken.message(key, String(slug)))\n if (address !== undefined && typeof slug === 'string') {\n const column = served.binding.columns[address.key]!\n const id = served.binding.columns.id!\n const others = yield* Effect.promise(() =>\n Promise.resolve(\n database\n .select({ id })\n .from(served.binding.table)\n .where(and(eq(column, slug), creating ? undefined : ne(id, entry.targetId!)))\n .limit(1),\n ),\n )\n if (others.length > 0) return yield* taken(address.key)\n }\n\n return yield* Effect.gen(function* () {\n const writes = yield* drizzleWrites\n const n = (held ?? 0) + 1\n // Compare and set, first: of two publishes made from one revision, one\n // finds the number already moved, and nothing of it is written.\n const moved = yield* Effect.promise(() =>\n Promise.resolve(\n writes\n .update(tables.entries)\n .set({ revision: n })\n .where(\n and(\n eq(tables.entries.id, entry.id),\n held === null\n ? isNull(tables.entries.revision)\n : eq(tables.entries.revision, held),\n ),\n )\n .returning({ id: tables.entries.id }),\n ),\n )\n if (moved.length === 0) return yield* conflict\n const ran = yield* handler.run({ input: decoded, principal })\n const targetId = creating\n ? String((ran.output as { readonly id: unknown }).id)\n : entry.targetId!\n const shown = served.type.roles.published\n if (shown !== undefined) {\n // Publishing shows the row. One already shown keeps its first date.\n const column = served.binding.columns[shown.key]!\n yield* Effect.promise(() =>\n Promise.resolve(\n writes\n .update(served.binding.table)\n .set({ [shown.key]: stamp(column, at) })\n .where(and(eq(served.binding.columns.id!, targetId), isNull(column)))\n .returning({ id: served.binding.columns.id! }),\n ),\n )\n }\n const revisionId = `${entry.id}:${n}`\n yield* Effect.promise(() =>\n Promise.resolve(\n writes.insert(tables.revisions).values({\n id: revisionId,\n entryId: entry.id,\n n,\n values: draft?.values,\n publishedAt: at.toISOString(),\n publishedBy: nameOf(principal),\n }),\n ),\n )\n yield* Effect.promise(() =>\n Promise.resolve(writes.delete(tables.drafts).where(eq(tables.drafts.id, entry.id))),\n )\n yield* Effect.promise(() =>\n Promise.resolve(\n writes\n .update(tables.entries)\n .set({ targetId })\n .where(eq(tables.entries.id, entry.id))\n .returning({ id: tables.entries.id }),\n ),\n )\n return {\n output: { entry: entry.id as never, targetId, revision: n },\n entities: [\n ...ran.entities,\n // The row as the handler left it, every column: a handler need not patch what it wrote.\n ...(yield* returning.row(served.binding, targetId)),\n ...(yield* entryPatches(entry.id)),\n ...revisionPatch.patches(\n yield* readRows(Db.Revision, revisionPatch.columns, revisionId),\n ),\n ],\n connections: ran.connections,\n deleted: [...ran.deleted, { entity: 'CmsDraft', id: entry.id }],\n }\n }).pipe(\n // The publish that lost the race: the index refused it, and it is the same\n // news as the check's. Any other defect is left as it was.\n Effect.catchDefect(defect =>\n address !== undefined &&\n refusedAsDuplicate(defect, served.binding.columns[address.key]!.name)\n ? Effect.fail(taken(address.key))\n : Effect.die(defect),\n ),\n )\n })\n\n const Publish = operation(Cms.Operations.Publish, ({ input, principal }) =>\n Effect.gen(function* () {\n const found = yield* offering(input.entry, 'publish')\n yield* asking(principal, 'publish', found.entry)\n return yield* publishing(found, principal, input.basedOn)\n }),\n )\n\n /** Writes to an entry's draft, and answers with both as the client should now hold them. */\n const drafted = (id: string, values: object) =>\n Effect.gen(function* () {\n const writes = yield* drizzleWrites\n yield* Effect.promise(() =>\n Promise.resolve(\n writes\n .update(tables.drafts)\n .set(values)\n .where(eq(tables.drafts.id, id))\n .returning({ id: tables.drafts.id }),\n ),\n )\n return {\n output: {},\n entities: [\n ...(yield* entryPatches(id)),\n ...scheduledPatch.patches(yield* readRows(Db.Draft, scheduledPatch.columns, id)),\n ],\n }\n })\n\n const Schedule = operation(Cms.Operations.Schedule, ({ input, principal }) =>\n Effect.gen(function* () {\n const found = yield* offering(input.entry, 'schedule')\n yield* asking(principal, 'schedule', found.entry)\n if (Number.isNaN(new Date(input.at).getTime()))\n return yield* refuse(`\"${input.at}\" is not a time`)\n // What cannot be published now is not promised for later.\n yield* readied(found)\n return yield* drafted(found.entry.id, {\n scheduledFor: new Date(input.at).toISOString(),\n scheduledBy: nameOf(principal),\n scheduleError: null,\n })\n }),\n )\n\n const Unschedule = operation(Cms.Operations.Unschedule, ({ input, principal }) =>\n Effect.gen(function* () {\n const found = yield* offering(input.entry, 'unschedule')\n yield* asking(principal, 'unschedule', found.entry)\n return yield* drafted(found.entry.id, {\n scheduledFor: null,\n scheduledBy: null,\n scheduleError: null,\n })\n }),\n )\n\n /** Archiving and its undoing: a fact about the entry, and for what can be hidden, the row. */\n const archiving = (transition: 'archive' | 'unarchive') =>\n operation(\n transition === 'archive' ? Cms.Operations.Archive : Cms.Operations.Unarchive,\n ({ input, principal }) =>\n Effect.gen(function* () {\n const { entry, served } = yield* offering(input.entry, transition)\n yield* asking(principal, transition, entry)\n const writes = yield* drizzleWrites\n yield* Effect.promise(() =>\n Promise.resolve(\n writes\n .update(tables.entries)\n .set({ archivedAt: transition === 'archive' ? now().toISOString() : null })\n .where(eq(tables.entries.id, entry.id))\n .returning({ id: tables.entries.id }),\n ),\n )\n const shown = served.type.roles.published\n if (transition === 'unarchive' || shown === undefined || entry.targetId === null)\n return { output: {}, entities: yield* entryPatches(entry.id) }\n // What is put away is not left on show. It comes back as unpublished work.\n yield* Effect.promise(() =>\n Promise.resolve(\n writes\n .update(served.binding.table)\n .set({ [shown.key]: null })\n .where(eq(served.binding.columns.id!, entry.targetId!))\n .returning({ id: served.binding.columns.id! }),\n ),\n )\n const content = returning(served.binding, [shown.key])\n return {\n output: {},\n entities: [\n ...content.patches(\n yield* readRows(served.binding, content.columns, entry.targetId),\n ),\n ...(yield* entryPatches(entry.id)),\n ],\n }\n }),\n )\n\n /**\n * A revision's value becomes the working copy, and nothing is published. It\n * replaces what the draft held, and takes back a promise made of that: what\n * was scheduled is not what is there now. The saved Model goes too, so an\n * editor fills its form from the values, key by key.\n */\n const Restore = operation(Cms.Operations.Restore, ({ input, principal }) =>\n Effect.gen(function* () {\n const { entry } = yield* offering(input.entry, 'restore')\n yield* asking(principal, 'restore', entry)\n yield* promised(principal, entry)\n const database = yield* DrizzleDatabase\n const [revision] = yield* Effect.promise(() =>\n Promise.resolve(\n database\n .select({ values: tables.revisions.values })\n .from(tables.revisions)\n .where(\n and(eq(tables.revisions.entryId, entry.id), eq(tables.revisions.n, input.revision)),\n )\n .limit(1),\n ),\n )\n if (revision === undefined)\n return yield* refuse(`This entry has no revision ${input.revision}`)\n\n const writes = yield* drizzleWrites\n const [held] = yield* readRows(Db.Draft, { updatedAt: tables.drafts.updatedAt }, entry.id)\n const at = now().toISOString()\n const updatedAt =\n held !== undefined && at <= String(held.updatedAt)\n ? new Date(new Date(String(held.updatedAt)).getTime() + 1).toISOString()\n : at\n const restored = {\n values: revision.values,\n model: null,\n form: `restored@${input.revision}`,\n updatedAt,\n updatedBy: nameOf(principal),\n baseRevision: entry.revision,\n scheduledFor: null,\n scheduledBy: null,\n scheduleError: null,\n }\n yield* Effect.promise(() =>\n Promise.resolve(\n held === undefined\n ? writes.insert(tables.drafts).values({ id: entry.id, ...restored })\n : writes\n .update(tables.drafts)\n .set(restored)\n .where(eq(tables.drafts.id, entry.id))\n .returning({ id: tables.drafts.id }),\n ),\n )\n return {\n output: { updatedAt },\n entities: [\n ...(yield* entryPatches(entry.id)),\n ...draftPatch.patches(yield* readRows(Db.Draft, draftPatch.columns, entry.id)),\n ...scheduledPatch.patches(yield* readRows(Db.Draft, scheduledPatch.columns, entry.id)),\n ],\n }\n }),\n )\n\n /**\n * Publishes every draft whose time has come, each in its own transaction, as\n * whoever scheduled it: `as` says who a stored name is. One that fails stays\n * scheduled with the reason, and is left alone until its draft changes, so a\n * fault the author must fix is not tried again every minute. The package owns\n * no timer: a cron trigger, an interval or a queue calls this.\n */\n const due = (at: Date, options: { readonly as: (scheduledBy: string | null) => P }) =>\n Effect.gen(function* () {\n const database = yield* DrizzleDatabase\n const waiting = yield* Effect.promise(() =>\n Promise.resolve(\n database\n .select({ id: tables.drafts.id, scheduledBy: tables.drafts.scheduledBy })\n .from(tables.drafts)\n .where(\n and(\n isNotNull(tables.drafts.scheduledFor),\n lte(tables.drafts.scheduledFor, at.toISOString()),\n isNull(tables.drafts.scheduleError),\n // What is put away keeps its promise, and keeps it waiting.\n sql`${tables.drafts.id} in (select ${tables.entries.id} from ${tables.entries} where ${tables.entries.archivedAt} is null)`,\n ),\n ),\n ),\n )\n const outcomes: Array<{ readonly entry: string; readonly error: string | null }> = []\n for (const draft of waiting) {\n const id = String(draft.id)\n const principal = options.as((draft.scheduledBy as string | null) ?? null)\n const error = yield* config\n .transaction(\n Effect.gen(function* () {\n if (!isAuthor(principal))\n return yield* refuse('Whoever scheduled this is no longer an author')\n const found = yield* offering(id, 'publish')\n yield* asking(principal, 'publish', found.entry)\n yield* publishing(found, principal, found.entry.revision)\n return null\n }),\n )\n .pipe(\n Effect.catch(failure => Effect.succeed(failure.message)),\n Effect.catchDefect(defect => Effect.succeed(String(defect))),\n )\n // Recorded after the failed publish was undone, or it would be undone with it.\n if (error !== null) yield* config.transaction(drafted(id, { scheduleError: error }))\n outcomes.push({ entry: id, error })\n }\n return outcomes\n })\n\n /**\n * Content published already, such as a site's first pages or what another CMS\n * held, written by the path a publish takes: an entry and a draft of\n * `values`, then the publish, which runs the type's own `create` handler as\n * `as` and records revision 1. It is the server's act, not an author's, so\n * `allow` is not asked. `at` is when it was published (default now); `entry`\n * names the entry (default a new id). One transaction: it all lands or none.\n */\n const importing = (item: ImportItem<P>) =>\n config.transaction(\n Effect.gen(function* () {\n const served = byType.get(item.type)\n if (served === undefined)\n return yield* refuse(`\"${item.type}\" is not a type of content this server knows`)\n const writes = yield* drizzleWrites\n const id = item.entry ?? Cms.newEntryId()\n const at = item.at ?? now()\n const labelKey = served.type.roles.label?.key\n const named =\n labelKey === undefined\n ? undefined\n : (item.values as Readonly<Record<string, unknown>> | null)?.[labelKey]\n const entry: EntryRow = {\n id,\n type: item.type,\n targetId: null,\n label: typeof named === 'string' ? named : '',\n archivedAt: null,\n revision: null,\n }\n yield* Effect.promise(() =>\n Promise.resolve(\n writes.insert(tables.entries).values({\n ...entry,\n createdBy: nameOf(item.as),\n createdAt: at.toISOString(),\n }),\n ),\n )\n yield* Effect.promise(() =>\n Promise.resolve(\n writes.insert(tables.drafts).values({\n id,\n values: item.values,\n model: null,\n form: '',\n updatedAt: at.toISOString(),\n updatedBy: nameOf(item.as),\n baseRevision: null,\n }),\n ),\n )\n const facts: Facts = {\n archivedAt: null,\n row: 'none',\n draft: { scheduledFor: null, scheduleError: null },\n }\n const published = yield* publishing({ entry, served, facts }, item.as, null, at)\n return { entry: id, targetId: published.output.targetId }\n }),\n )\n\n const Unpublish = operation(Cms.Operations.Unpublish, ({ input, principal }) =>\n Effect.gen(function* () {\n const { entry, served } = yield* offering(input.entry, 'unpublish')\n yield* asking(principal, 'unpublish', entry)\n const shown = served.type.roles.published!\n const writes = yield* drizzleWrites\n yield* Effect.promise(() =>\n Promise.resolve(\n writes\n .update(served.binding.table)\n .set({ [shown.key]: null })\n .where(eq(served.binding.columns.id!, entry.targetId!))\n .returning({ id: served.binding.columns.id! }),\n ),\n )\n const content = returning(served.binding, [shown.key])\n return {\n output: {},\n entities: [\n ...content.patches(yield* readRows(served.binding, content.columns, entry.targetId!)),\n ...(yield* entryPatches(entry.id)),\n ],\n }\n }),\n )\n\n const sources: ReadonlyArray<EntitySource<P, DrizzleDatabase>> = [\n entries,\n source<P>(Db.Draft),\n source<P>(Db.Revision),\n ...config.content.map(served => source<P>(served.binding)),\n ]\n const mutations: ReadonlyArray<MutationSource<P, DrizzleDatabase>> = [\n SaveDraft,\n DiscardDraft,\n Publish,\n Unpublish,\n Schedule,\n Unschedule,\n archiving('archive'),\n archiving('unarchive'),\n Restore,\n ]\n\n // A content type with an address is found by it, behind the same boundary as\n // every other read of its table.\n // The descriptor carries what the query means, so this registers the source\n // and says no more: the address column and the order come from the body,\n // through the binding that knows which column holds which field.\n const bySlug = config.content.flatMap(({ type, binding }) =>\n type.roles.slug === undefined ? [] : [query(Cms.bySlug(type), { entity: binding })],\n )\n\n return {\n /**\n * The sources of the content types and of the CMS's own Entities, each behind\n * the audience boundary. Register these and not `source(binding)` of a content\n * table: that is how the boundary cannot be forgotten.\n */\n sources,\n queries: [worklist, ...bySlug] as ReadonlyArray<QuerySource<P, DrizzleDatabase>>,\n mutations,\n due,\n import: importing,\n /** The bindings of `Entry`, `Draft` and `Revision`, for a handler that returns patches of them. */\n bindings: Db,\n }\n },\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foldkit-cms-drizzle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "The server half of foldkit-cms over Drizzle: the entries, drafts and revisions tables, the draft operations with their conflict rule, the worklist query, and the audience boundary between a visitor and an author.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"foldkit",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"sideEffects": false,
|
|
29
29
|
"exports": {
|
|
30
30
|
".": {
|
|
31
|
+
"foldkit-plus:source": "./src/index.ts",
|
|
31
32
|
"types": "./dist/index.d.mts",
|
|
32
33
|
"import": "./dist/index.mjs"
|
|
33
34
|
}
|
|
@@ -39,23 +40,24 @@
|
|
|
39
40
|
],
|
|
40
41
|
"dependencies": {
|
|
41
42
|
"drizzle-orm": "1.0.0-rc.4",
|
|
42
|
-
"foldkit-
|
|
43
|
-
"foldkit-remote
|
|
44
|
-
"foldkit-remote": "0.
|
|
45
|
-
"foldkit-
|
|
46
|
-
"foldkit-remote-
|
|
43
|
+
"foldkit-cms": "0.3.0",
|
|
44
|
+
"foldkit-remote": "0.9.0",
|
|
45
|
+
"foldkit-remote-server": "0.9.0",
|
|
46
|
+
"foldkit-entity": "0.5.0",
|
|
47
|
+
"foldkit-remote-drizzle": "0.8.1"
|
|
47
48
|
},
|
|
48
49
|
"peerDependencies": {
|
|
49
|
-
"effect": "
|
|
50
|
+
"effect": ">=4.0.0-rc.116 <4.0.0-rc.118"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
|
-
"effect": "4.0.0-rc.
|
|
53
|
-
"
|
|
53
|
+
"@effect/platform-browser": "4.0.0-rc.116",
|
|
54
|
+
"effect": "4.0.0-rc.116",
|
|
55
|
+
"foldkit": "0.163.0",
|
|
54
56
|
"tsdown": "^0.23.0",
|
|
55
57
|
"typescript": "5.9.3",
|
|
56
|
-
"foldkit-
|
|
57
|
-
"foldkit-
|
|
58
|
-
"foldkit-surface": "0.
|
|
58
|
+
"foldkit-form": "0.3.0",
|
|
59
|
+
"foldkit-bundle": "0.4.0",
|
|
60
|
+
"foldkit-surface": "0.5.1"
|
|
59
61
|
},
|
|
60
62
|
"scripts": {
|
|
61
63
|
"build": "tsdown",
|