@thomasfosterau/pulpit 0.0.0 → 0.1.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.
Files changed (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1271 -1
  3. package/dist/blobs.d.ts +249 -0
  4. package/dist/blobs.d.ts.map +1 -0
  5. package/dist/blobs.js +193 -0
  6. package/dist/blobs.js.map +1 -0
  7. package/dist/budgets.d.ts +255 -0
  8. package/dist/budgets.d.ts.map +1 -0
  9. package/dist/budgets.js +211 -0
  10. package/dist/budgets.js.map +1 -0
  11. package/dist/cache.d.ts +154 -0
  12. package/dist/cache.d.ts.map +1 -0
  13. package/dist/cache.js +118 -0
  14. package/dist/cache.js.map +1 -0
  15. package/dist/collections.d.ts +242 -0
  16. package/dist/collections.d.ts.map +1 -0
  17. package/dist/collections.js +215 -0
  18. package/dist/collections.js.map +1 -0
  19. package/dist/comments.d.ts +212 -0
  20. package/dist/comments.d.ts.map +1 -0
  21. package/dist/comments.js +192 -0
  22. package/dist/comments.js.map +1 -0
  23. package/dist/content.d.ts +231 -0
  24. package/dist/content.d.ts.map +1 -0
  25. package/dist/content.js +204 -0
  26. package/dist/content.js.map +1 -0
  27. package/dist/documents.d.ts +184 -0
  28. package/dist/documents.d.ts.map +1 -0
  29. package/dist/documents.js +190 -0
  30. package/dist/documents.js.map +1 -0
  31. package/dist/edges.d.ts +645 -0
  32. package/dist/edges.d.ts.map +1 -0
  33. package/dist/edges.js +558 -0
  34. package/dist/edges.js.map +1 -0
  35. package/dist/filter.d.ts +461 -0
  36. package/dist/filter.d.ts.map +1 -0
  37. package/dist/filter.js +387 -0
  38. package/dist/filter.js.map +1 -0
  39. package/dist/index.d.ts +133 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +133 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/internal/filter-columns.d.ts +8 -0
  44. package/dist/internal/filter-columns.d.ts.map +1 -0
  45. package/dist/internal/filter-columns.js +43 -0
  46. package/dist/internal/filter-columns.js.map +1 -0
  47. package/dist/internal/list-order.d.ts +26 -0
  48. package/dist/internal/list-order.d.ts.map +1 -0
  49. package/dist/internal/list-order.js +92 -0
  50. package/dist/internal/list-order.js.map +1 -0
  51. package/dist/internal/owned-columns.d.ts +93 -0
  52. package/dist/internal/owned-columns.d.ts.map +1 -0
  53. package/dist/internal/owned-columns.js +98 -0
  54. package/dist/internal/owned-columns.js.map +1 -0
  55. package/dist/internal/plain-rows.d.ts +32 -0
  56. package/dist/internal/plain-rows.d.ts.map +1 -0
  57. package/dist/internal/plain-rows.js +32 -0
  58. package/dist/internal/plain-rows.js.map +1 -0
  59. package/dist/internal/reference-columns.d.ts +80 -0
  60. package/dist/internal/reference-columns.d.ts.map +1 -0
  61. package/dist/internal/reference-columns.js +102 -0
  62. package/dist/internal/reference-columns.js.map +1 -0
  63. package/dist/moderation.d.ts +392 -0
  64. package/dist/moderation.d.ts.map +1 -0
  65. package/dist/moderation.js +375 -0
  66. package/dist/moderation.js.map +1 -0
  67. package/dist/paths.d.ts +204 -0
  68. package/dist/paths.d.ts.map +1 -0
  69. package/dist/paths.js +165 -0
  70. package/dist/paths.js.map +1 -0
  71. package/dist/proposals.d.ts +523 -0
  72. package/dist/proposals.d.ts.map +1 -0
  73. package/dist/proposals.js +505 -0
  74. package/dist/proposals.js.map +1 -0
  75. package/dist/publication.d.ts +148 -0
  76. package/dist/publication.d.ts.map +1 -0
  77. package/dist/publication.js +183 -0
  78. package/dist/publication.js.map +1 -0
  79. package/dist/purge.d.ts +224 -0
  80. package/dist/purge.d.ts.map +1 -0
  81. package/dist/purge.js +151 -0
  82. package/dist/purge.js.map +1 -0
  83. package/dist/reactions.d.ts +246 -0
  84. package/dist/reactions.d.ts.map +1 -0
  85. package/dist/reactions.js +183 -0
  86. package/dist/reactions.js.map +1 -0
  87. package/dist/registry.d.ts +316 -0
  88. package/dist/registry.d.ts.map +1 -0
  89. package/dist/registry.js +305 -0
  90. package/dist/registry.js.map +1 -0
  91. package/dist/search.d.ts +366 -0
  92. package/dist/search.d.ts.map +1 -0
  93. package/dist/search.js +273 -0
  94. package/dist/search.js.map +1 -0
  95. package/dist/social.d.ts +311 -0
  96. package/dist/social.d.ts.map +1 -0
  97. package/dist/social.js +205 -0
  98. package/dist/social.js.map +1 -0
  99. package/dist/subtype.d.ts +1613 -0
  100. package/dist/subtype.d.ts.map +1 -0
  101. package/dist/subtype.js +1300 -0
  102. package/dist/subtype.js.map +1 -0
  103. package/dist/testing.d.ts +675 -0
  104. package/dist/testing.d.ts.map +1 -0
  105. package/dist/testing.js +1022 -0
  106. package/dist/testing.js.map +1 -0
  107. package/dist/version-chain.d.ts +702 -0
  108. package/dist/version-chain.d.ts.map +1 -0
  109. package/dist/version-chain.js +489 -0
  110. package/dist/version-chain.js.map +1 -0
  111. package/dist/working-copy.d.ts +116 -0
  112. package/dist/working-copy.d.ts.map +1 -0
  113. package/dist/working-copy.js +95 -0
  114. package/dist/working-copy.js.map +1 -0
  115. package/package.json +138 -4
  116. package/src/blobs.ts +384 -0
  117. package/src/budgets.ts +338 -0
  118. package/src/cache.ts +218 -0
  119. package/src/collections.ts +427 -0
  120. package/src/comments.ts +392 -0
  121. package/src/content.ts +238 -0
  122. package/src/documents.ts +284 -0
  123. package/src/edges.ts +1185 -0
  124. package/src/filter.ts +523 -0
  125. package/src/index.ts +153 -0
  126. package/src/internal/filter-columns.ts +50 -0
  127. package/src/internal/list-order.ts +117 -0
  128. package/src/internal/owned-columns.ts +140 -0
  129. package/src/internal/plain-rows.ts +54 -0
  130. package/src/internal/reference-columns.ts +156 -0
  131. package/src/moderation.ts +728 -0
  132. package/src/paths.ts +267 -0
  133. package/src/proposals.ts +1014 -0
  134. package/src/publication.ts +225 -0
  135. package/src/purge.ts +313 -0
  136. package/src/reactions.ts +347 -0
  137. package/src/registry.ts +524 -0
  138. package/src/search.ts +540 -0
  139. package/src/social.ts +469 -0
  140. package/src/subtype.ts +3095 -0
  141. package/src/testing.ts +1873 -0
  142. package/src/version-chain.ts +1093 -0
  143. package/src/working-copy.ts +192 -0
@@ -0,0 +1,204 @@
1
+ /**
2
+ * **The Content model half of the kernel** — the shared Content-axis field
3
+ * spreads and the base `content` resource, defined via
4
+ * `@thomasfosterau/effect-jsonapi`'s `Resource.make`.
5
+ *
6
+ * Ported from gardeneria's `packages/models/src/models/content.ts` (the
7
+ * three-state `Visibility`; website's copy is the older two-state one) and the
8
+ * timestamp / tombstone spreads of its `packages/models/src/utilities.ts`. A
9
+ * `#NNN` here names a gardeneria issue.
10
+ *
11
+ * Every spread is a plain `const` field map a Content subtype spreads into its
12
+ * own `Resource.make({ attributes })`, so one declaration drives the resource
13
+ * object, its documents and the create / update projections (the tri-state
14
+ * rule: a value sets, `null` clears a nullable column, `undefined` carries
15
+ * forward — declared on the descriptor, never re-spelled at a call site).
16
+ *
17
+ * @copyright Thomas Foster
18
+ * @module pulpit/content
19
+ * @since 0.1.0
20
+ */
21
+ import { Relationship, Resource } from "@thomasfosterau/effect-jsonapi";
22
+ import * as Schema from "effect/Schema";
23
+ /**
24
+ * Visibility — the explicit access axis the publication policy reads,
25
+ * independent of soft-delete and the draft flag. Three states, because the
26
+ * kernel is multi-tenant:
27
+ *
28
+ * - `public` — anyone, signed in or not; the only state the KV read-through
29
+ * cache may ever hold (`isPubliclyServeable` in `./publication`);
30
+ * - `followers` — the owner, the owner's followers (the follower seam in
31
+ * `./subtype`), and the privileged rung;
32
+ * - `private` — the owner and the privileged rung only.
33
+ *
34
+ * The predicate lives in ONE place — `canViewOwned` in `./subtype` — and
35
+ * every read of an owned row goes through it. Content carries it on the
36
+ * Content branch's version row; owned Entity / File leaves carry the same
37
+ * column on their own version row, declared with
38
+ * {@link ownedVisibilityResourceFields}.
39
+ *
40
+ * @since 0.1.0
41
+ */
42
+ export const Visibility = Schema.Literals(["public", "followers", "private"]);
43
+ /**
44
+ * The two orthogonal Content-branch axes every Content subtype carries on its
45
+ * version row: `visibility` (three-state, above) and the binary `draft` flag
46
+ * (`PUB-009` / `PUB-010`). Both are required on the resource but optional to
47
+ * supply at create / update (a DB default applies, updates carry forward);
48
+ * neither is ever cleared.
49
+ *
50
+ * `Resource.attribute(schema, { create: "optional" })` is the
51
+ * required-on-resource, optional-at-both-inputs, never-cleared descriptor.
52
+ *
53
+ * @since 0.1.0
54
+ */
55
+ export const contentVisibilityResourceFields = {
56
+ visibility: Resource.attribute(Visibility, { create: "optional" }),
57
+ draft: Resource.attribute(Schema.Boolean, { create: "optional" }),
58
+ };
59
+ /**
60
+ * The visibility / draft fields as **create-input** shapes (`Schema.optional`,
61
+ * non-null), for Content subtypes' hand-written `Create` structs — a form
62
+ * adapter passes `value | undefined`, which `createInput`'s strict
63
+ * `optionalKey` rejects, so the create struct is hand-written.
64
+ *
65
+ * @since 0.1.0
66
+ */
67
+ export const contentVisibilityCreateFields = {
68
+ visibility: Schema.optional(Visibility),
69
+ draft: Schema.optional(Schema.Boolean),
70
+ };
71
+ /**
72
+ * The visibility axis alone, for **owned Entity / File leaves** (a garden, an
73
+ * organism, an image): they carry `visibility` on their own version row but
74
+ * no `draft` flag — a garden is not a draft of anything. Same descriptor as
75
+ * the Content pair: required on the resource, optional at both inputs, never
76
+ * cleared.
77
+ *
78
+ * @since 0.1.0
79
+ */
80
+ export const ownedVisibilityResourceFields = {
81
+ visibility: Resource.attribute(Visibility, { create: "optional" }),
82
+ };
83
+ /**
84
+ * The version-chain timestamps every node-family resource carries:
85
+ * `createdAt` (the node's version-0 commit time) and `updatedAt` (its current
86
+ * version's), server-computed and present on the resource only
87
+ * (`readOnlyAttribute`), never accepted as command input.
88
+ *
89
+ * @since 0.1.0
90
+ */
91
+ export const nodeTimestampResourceFields = {
92
+ createdAt: Resource.readOnlyAttribute(Schema.Date),
93
+ updatedAt: Resource.readOnlyAttribute(Schema.Date),
94
+ };
95
+ /**
96
+ * The soft-delete tombstone: `deletedAt` is present only on a tombstoned row
97
+ * (`resource: "optional"` — emitters omit the key entirely while live, #691)
98
+ * and is in no write projection. A tombstone is written by the chain's
99
+ * `delete` operation (`./version-chain`), never by an input.
100
+ *
101
+ * @since 0.1.0
102
+ */
103
+ export const tombstoneResourceFields = {
104
+ deletedAt: Resource.attribute(Schema.Date, {
105
+ resource: "optional",
106
+ create: false,
107
+ update: false,
108
+ }),
109
+ };
110
+ /**
111
+ * The attribute spread every **reference-data** leaf carries: the two
112
+ * timestamps and the tombstone. Reference data has no visibility axis of its
113
+ * own — the corpus is publicly readable; a Content-branch reference leaf reads
114
+ * `visibility` off the branch row like every other Content subtype, and shows
115
+ * only a `public` row below the view-private rung.
116
+ *
117
+ * @since 0.1.0
118
+ */
119
+ export const referenceLeafResourceFields = {
120
+ ...nodeTimestampResourceFields,
121
+ ...tombstoneResourceFields,
122
+ };
123
+ /**
124
+ * The attribute spread every **owned Content leaf** opens its attributes with:
125
+ * the timestamps, the tombstone, and the access axis —
126
+ * {@link referenceLeafResourceFields} plus {@link ownedVisibilityResourceFields}.
127
+ *
128
+ * `draft` is deliberately absent from THIS shared spread. The family that
129
+ * actually wants a bare boolean "come back and finish this later" flag
130
+ * declares it directly through {@link contentVisibilityResourceFields};
131
+ * folding it in here would put an unused, always-`false` `draft` attribute on
132
+ * every other owned Content leaf's wire contract. A leaf gated by a
133
+ * publication *stamp* (`publishedAt`, the axis `./publication` reads) carries
134
+ * that on its own row instead — a different axis from `draft`'s hard gate
135
+ * (#1118).
136
+ *
137
+ * @since 0.1.0
138
+ */
139
+ export const ownedContentLeafResourceFields = {
140
+ ...referenceLeafResourceFields,
141
+ ...ownedVisibilityResourceFields,
142
+ };
143
+ /**
144
+ * The `owner` relationship every **owned** leaf carries — the account family
145
+ * the application supplies (its `users` resource) that owns the row.
146
+ *
147
+ * Server-assigned from the authenticated caller at creation and never
148
+ * re-parented: the operation layer stamps the owner from the viewer rather
149
+ * than from the payload (`stampOwner` in `./subtype`), so an application's
150
+ * hand-written create / update structs omit it. Spread into a leaf's
151
+ * `Resource.make({ relationships })` to opt in — the registry flags the same
152
+ * leaf `owned`, and the subtype factory's `access: { kind: "owned", … }`
153
+ * names the column the linkage reads.
154
+ *
155
+ * `Relationship.one`: every owned row has an owner. The target may still fail
156
+ * to resolve (deleting an account does not cascade to its rows); consumers
157
+ * degrade to a neutral label rather than erroring.
158
+ *
159
+ * Note that effect-jsonapi lists a `one` relationship as **required** in
160
+ * the generated `createPayload`; an application serving the generated payload
161
+ * strips the linkage at its handler seam. effect-jsonapi has no read-only
162
+ * linkage descriptor (effect-jsonapi#172 closed without one; 0.17.0 has
163
+ * none), and `tests/content.test.ts` pins the required linkage so the day one
164
+ * ships is visible.
165
+ *
166
+ * @since 0.1.0
167
+ */
168
+ export const ownerRef = (owner) => ({
169
+ owner: Relationship.one(owner),
170
+ });
171
+ /**
172
+ * The base Content resource's attributes: the timestamps and the tombstone —
173
+ * exactly {@link referenceLeafResourceFields}, named for the resource that
174
+ * carries them so a subtype extending the base and a leaf spreading the
175
+ * fields declare the same wire shape.
176
+ *
177
+ * @since 0.1.0
178
+ */
179
+ export const contentResourceAttributeFields = referenceLeafResourceFields;
180
+ /**
181
+ * Build the base `content` resource with the application's own id brand
182
+ * (`makeContentResource(ContentId)`) — the shape {@link ContentResource}
183
+ * carries with the auto-derived `Resource.Id<"content">`. An application whose
184
+ * ids are branded hierarchically (a `PostId` that is also a `ContentId` and a
185
+ * `NodeId`) declares the base with its own brand so the two agree.
186
+ *
187
+ * @since 0.1.0
188
+ */
189
+ export const makeContentResource = (id) => Resource.make("content", { id, attributes: contentResourceAttributeFields });
190
+ /**
191
+ * The base Content resource with the auto-derived id (timestamps + soft-delete tombstone).
192
+ *
193
+ * @since 0.1.0
194
+ */
195
+ export const ContentResource = Resource.make("content", {
196
+ attributes: contentResourceAttributeFields,
197
+ });
198
+ /**
199
+ * Lightweight `{ type, id }` reference to content.
200
+ *
201
+ * @since 0.1.0
202
+ */
203
+ export const ContentRef = ContentResource.identifier;
204
+ //# sourceMappingURL=content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.js","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC,CAAC;AAQ9E;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG;IAC7C,UAAU,EAAE,QAAQ,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAClE,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;CACzD,CAAC;AAEX;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG;IAC3C,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC;IACvC,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC;CAC9B,CAAC;AAEX;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG;IAC3C,UAAU,EAAE,QAAQ,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;CAC1D,CAAC;AAEX;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC,SAAS,EAAE,QAAQ,CAAC,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC;IAClD,SAAS,EAAE,QAAQ,CAAC,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC;CAC1C,CAAC;AAEX;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE;QACzC,QAAQ,EAAE,UAAU;QACpB,MAAM,EAAE,KAAK;QACb,MAAM,EAAE,KAAK;KACd,CAAC;CACM,CAAC;AAEX;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC,GAAG,2BAA2B;IAC9B,GAAG,uBAAuB;CAClB,CAAC;AAEX;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG;IAC5C,GAAG,2BAA2B;IAC9B,GAAG,6BAA6B;CACxB,CAAC;AAEX;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAA6B,KAAkB,EAAE,EAAE,CAAC,CAAC;IAC3E,KAAK,EAAE,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC;CAC/B,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,2BAA2B,CAAC;AAE1E;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAA6C,EAAY,EAAE,EAAE,CAC9F,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,8BAA8B,EAAE,CAAC,CAAC;AAE/E;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE;IACtD,UAAU,EAAE,8BAA8B;CAC3C,CAAC,CAAC;AASH;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,eAAe,CAAC,UAAU,CAAC"}
@@ -0,0 +1,184 @@
1
+ /**
2
+ * **Content-addressed documents** — the store seam a version row's document
3
+ * reference resolves through, the hash it is keyed by, and the field spread
4
+ * that carries the reference.
5
+ *
6
+ * A document (a ProseKit tree, in the apps) is stored once, keyed by a hash of
7
+ * its content, and a version row carries the hash rather than another full
8
+ * copy. Under an append-only chain that matters: history is never pruned
9
+ * (`VERSION-CHAIN-001`), so without content addressing every version of a long
10
+ * document would store the whole document again. The behavioural spec is
11
+ * `packages/database/src/documents.ts` in gardeneria and website (the
12
+ * `inline_documents` / `block_documents` writers) and the generated `hash_id`
13
+ * column of those tables.
14
+ *
15
+ * ## The invariant
16
+ *
17
+ * - **A stored document is never rewritten** (`DOC-001`). `put` stores a
18
+ * document only when its hash is unseen and otherwise writes nothing; a
19
+ * changed document is a new hash. So a hash a version row recorded always
20
+ * reads back the exact document it was written with, and `restoreVersion`
21
+ * restores the reference, never a copy. Only erasure (`./purge`) deletes a
22
+ * document, once no version of any node references its hash.
23
+ *
24
+ * ## The seam
25
+ *
26
+ * Hashing is the store's job, not the kernel's: the apps derive the hash in
27
+ * Postgres and never re-derive it in JavaScript. The algorithm and the
28
+ * canonical serialisation a store must use are stated in the package README →
29
+ * Seams; {@link canonicalText} is that serialisation in JavaScript, for a
30
+ * store that doesn't run on Postgres. The kernel itself never hashes. The
31
+ * document type is a parameter; its schema and validation are the
32
+ * application's (prosekit-utilities), and pulpit never imports them.
33
+ *
34
+ * @copyright Thomas Foster
35
+ * @module pulpit/documents
36
+ * @since 0.1.0
37
+ */
38
+ import { Resource } from "@thomasfosterau/effect-jsonapi";
39
+ import * as Effect from "effect/Effect";
40
+ import type * as HashMap from "effect/HashMap";
41
+ import type * as Option from "effect/Option";
42
+ import * as Schema from "effect/Schema";
43
+ /**
44
+ * A document's content hash as the kernel exchanges it: the 32-byte SHA-256
45
+ * digest as 64 lowercase hexadecimal characters (what gardeneria's
46
+ * `byteaToHex` renders a `bytea` hash as). A Postgres store keeps the 32 bytes
47
+ * in a `bytea` column and converts at its edge. The algorithm and the bytes it
48
+ * is taken over are the contract in the package README → Seams.
49
+ *
50
+ * @since 0.1.0
51
+ */
52
+ export declare const DocumentHash: Schema.String;
53
+ /**
54
+ * The {@link DocumentHash} string: 64 lowercase hexadecimal characters.
55
+ *
56
+ * @since 0.1.0
57
+ */
58
+ export type DocumentHash = typeof DocumentHash.Type;
59
+ /**
60
+ * What {@link DocumentStore.put} returns: the document's hash, and whether
61
+ * this call inserted it (`false` for a document already stored).
62
+ *
63
+ * @since 0.1.0
64
+ */
65
+ export interface DocumentPut {
66
+ readonly hash: DocumentHash;
67
+ readonly inserted: boolean;
68
+ }
69
+ /**
70
+ * The content-addressed document store an application supplies (in
71
+ * production, a table per document family, keyed by the hash). `Document`
72
+ * is the application's document type; `E` is the store's own failure and `R`
73
+ * its requirements (a Drizzle store's `EffectDrizzleQueryError` and
74
+ * `CurrentTransaction`: like the chain store, it never opens a transaction).
75
+ *
76
+ * Every method fails only with the store's `E`, and applies no policy: a
77
+ * document is validated against the application's schema before it reaches
78
+ * `put`. Honours `DOC-001`: `put` never rewrites a stored document.
79
+ *
80
+ * @since 0.1.0
81
+ */
82
+ export interface DocumentStore<Document, E, R> {
83
+ /**
84
+ * Store `document` if its hash is unseen and return the hash either way
85
+ * (`INSERT … ON CONFLICT DO NOTHING RETURNING`, then the hash), with
86
+ * `inserted: true` only when this call wrote the row (the `RETURNING`
87
+ * gave one back). Idempotent: a document already stored, or one equal to
88
+ * it under the canonical serialisation, writes nothing and returns the
89
+ * same hash with `inserted: false`. The hash is the store's to derive
90
+ * (README → Seams).
91
+ */
92
+ readonly put: (document: Document) => Effect.Effect<DocumentPut, E, R>;
93
+ /** The document stored at `hash`, or none. */
94
+ readonly get: (hash: DocumentHash) => Effect.Effect<Option.Option<Document>, E, R>;
95
+ /**
96
+ * One `hash = ANY($hashes)` read, never `hashes.length` sequential gets: the
97
+ * documents found, keyed by hash. A hash with no document is absent from
98
+ * the map.
99
+ */
100
+ readonly getMany: (hashes: ReadonlyArray<DocumentHash>) => Effect.Effect<HashMap.HashMap<DocumentHash, Document>, E, R>;
101
+ }
102
+ /**
103
+ * What an {@link onDocumentInsert} hook is shown: a document this `put`
104
+ * stored for the first time, and its hash.
105
+ *
106
+ * @since 0.1.0
107
+ */
108
+ export interface DocumentInserted<Document> {
109
+ readonly hash: DocumentHash;
110
+ readonly document: Document;
111
+ }
112
+ /**
113
+ * The document store with a first-insert hook: its `put` runs `hook` after
114
+ * a `put` that inserted (`inserted: true`), and never after one that found
115
+ * the document already stored, so the hook runs once per document (an
116
+ * embedding job, in the apps). The hook runs in the `put`'s own
117
+ * transaction, so a failure (the store's `E`) fails the `put` and rolls
118
+ * the insert back with the caller's transaction; enqueue durable work as a
119
+ * row the same transaction writes (an outbox). `get` and `getMany` are the
120
+ * store's own.
121
+ *
122
+ * @example
123
+ * ```ts
124
+ * import { deepStrictEqual } from "node:assert";
125
+ * import * as Effect from "effect/Effect";
126
+ * import { onDocumentInsert } from "@thomasfosterau/pulpit/documents";
127
+ * import { makeInMemoryDocumentStore } from "@thomasfosterau/pulpit/testing";
128
+ *
129
+ * const inserted: Array<string> = [];
130
+ * const store = onDocumentInsert(makeInMemoryDocumentStore<{ readonly text: string }>(), (event) =>
131
+ * Effect.sync(() => {
132
+ * inserted.push(event.document.text);
133
+ * }),
134
+ * );
135
+ * await Effect.runPromise(Effect.andThen(store.put({ text: "hello" }), store.put({ text: "hello" })));
136
+ * deepStrictEqual(inserted, ["hello"]);
137
+ * ```
138
+ *
139
+ * @since 0.1.0
140
+ */
141
+ export declare const onDocumentInsert: <Document, E, R>(store: DocumentStore<Document, E, R>, hook: (event: DocumentInserted<Document>) => Effect.Effect<void, E, R>) => DocumentStore<Document, E, R>;
142
+ /**
143
+ * The canonical text of a JSON document, the bytes the {@link DocumentStore}
144
+ * hash contract (README → Seams → `DocumentStore`) takes SHA-256 over:
145
+ * PostgreSQL's `jsonb` text output, with object keys ordered shorter-first by
146
+ * UTF-8 byte length and then bytewise, `": "` after a key, `", "` between
147
+ * members and elements, only `"`, `\` and the control characters escaped, and
148
+ * numbers as `numeric` prints them (never with an exponent). A Postgres store
149
+ * never needs it (the database derives the hash from `document_jsonb::text`);
150
+ * a store that doesn't run on Postgres hashes the UTF-8 bytes of this text to
151
+ * reproduce the contract's hashes. The kernel's suite pins it to text and
152
+ * hashes taken from Postgres. Pure and total.
153
+ *
154
+ * @example
155
+ * ```ts
156
+ * import { strictEqual } from "node:assert";
157
+ * import { canonicalText } from "@thomasfosterau/pulpit/documents";
158
+ *
159
+ * strictEqual(
160
+ * canonicalText({ b: 1e21, aa: [true, null], a: "é" }),
161
+ * '{"a": "é", "b": 1000000000000000000000, "aa": [true, null]}',
162
+ * );
163
+ * ```
164
+ *
165
+ * @since 0.1.0
166
+ */
167
+ export declare const canonicalText: (value: Schema.Json) => string;
168
+ /**
169
+ * The attribute spread for a version row's document reference: `key` carries
170
+ * the {@link DocumentHash} a {@link DocumentStore} returned from `put` (its `hash`).
171
+ * Required on the resource and at create; optional at update, where an absent
172
+ * key carries the previous version's reference forward (the tri-state rule),
173
+ * and never cleared. Spread into a leaf's `Resource.make({ attributes })`,
174
+ * once per document column (`...documentRef("title")`,
175
+ * `...documentRef("body")`).
176
+ *
177
+ * A document the leaf may omit is declared directly, as
178
+ * `Resource.attribute(Schema.NullOr(DocumentHash), { create: "optional" })`,
179
+ * so that `null` clears it.
180
+ *
181
+ * @since 0.1.0
182
+ */
183
+ export declare const documentRef: <const Key extends string>(key: Key) => Record<Key, Resource.Attribute<Schema.String, true, "required", "optional", false>>;
184
+ //# sourceMappingURL=documents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../src/documents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAE1D,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,KAAK,MAAM,MAAM,eAAe,CAAC;AAI7C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAIxC;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,eAIxB,CAAC;AACF;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAC;AAIpD;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,aAAa,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC;IAC3C;;;;;;;;OAQG;IACH,QAAQ,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACvE,8CAA8C;IAC9C,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACnF;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,CAChB,MAAM,EAAE,aAAa,CAAC,YAAY,CAAC,KAChC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;CACnE;AAID;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB,CAAC,QAAQ;IACxC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAQ,EAAE,CAAC,EAAE,CAAC,SACtC,aAAa,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,QAC9B,CAAC,KAAK,EAAE,gBAAgB,CAAC,QAAQ,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,KACrE,aAAa,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,CAM7B,CAAC;AAwDH;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,aAAa,UAAW,MAAM,CAAC,IAAI,KAAG,MAWlD,CAAC;AAIF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,CAAC,GAAG,SAAS,MAAM,OAAO,GAAG,wFACL,CAAC"}
@@ -0,0 +1,190 @@
1
+ /**
2
+ * **Content-addressed documents** — the store seam a version row's document
3
+ * reference resolves through, the hash it is keyed by, and the field spread
4
+ * that carries the reference.
5
+ *
6
+ * A document (a ProseKit tree, in the apps) is stored once, keyed by a hash of
7
+ * its content, and a version row carries the hash rather than another full
8
+ * copy. Under an append-only chain that matters: history is never pruned
9
+ * (`VERSION-CHAIN-001`), so without content addressing every version of a long
10
+ * document would store the whole document again. The behavioural spec is
11
+ * `packages/database/src/documents.ts` in gardeneria and website (the
12
+ * `inline_documents` / `block_documents` writers) and the generated `hash_id`
13
+ * column of those tables.
14
+ *
15
+ * ## The invariant
16
+ *
17
+ * - **A stored document is never rewritten** (`DOC-001`). `put` stores a
18
+ * document only when its hash is unseen and otherwise writes nothing; a
19
+ * changed document is a new hash. So a hash a version row recorded always
20
+ * reads back the exact document it was written with, and `restoreVersion`
21
+ * restores the reference, never a copy. Only erasure (`./purge`) deletes a
22
+ * document, once no version of any node references its hash.
23
+ *
24
+ * ## The seam
25
+ *
26
+ * Hashing is the store's job, not the kernel's: the apps derive the hash in
27
+ * Postgres and never re-derive it in JavaScript. The algorithm and the
28
+ * canonical serialisation a store must use are stated in the package README →
29
+ * Seams; {@link canonicalText} is that serialisation in JavaScript, for a
30
+ * store that doesn't run on Postgres. The kernel itself never hashes. The
31
+ * document type is a parameter; its schema and validation are the
32
+ * application's (prosekit-utilities), and pulpit never imports them.
33
+ *
34
+ * @copyright Thomas Foster
35
+ * @module pulpit/documents
36
+ * @since 0.1.0
37
+ */
38
+ import { Resource } from "@thomasfosterau/effect-jsonapi";
39
+ import * as Arr from "effect/Array";
40
+ import * as Effect from "effect/Effect";
41
+ import * as Order from "effect/Order";
42
+ import * as Predicate from "effect/Predicate";
43
+ import * as Record from "effect/Record";
44
+ import * as Schema from "effect/Schema";
45
+ // ── Vocabulary ────────────────────────────────────────────────────────
46
+ /**
47
+ * A document's content hash as the kernel exchanges it: the 32-byte SHA-256
48
+ * digest as 64 lowercase hexadecimal characters (what gardeneria's
49
+ * `byteaToHex` renders a `bytea` hash as). A Postgres store keeps the 32 bytes
50
+ * in a `bytea` column and converts at its edge. The algorithm and the bytes it
51
+ * is taken over are the contract in the package README → Seams.
52
+ *
53
+ * @since 0.1.0
54
+ */
55
+ export const DocumentHash = Schema.String.check(Schema.isPattern(/^[0-9a-f]{64}$/, {
56
+ description: "a SHA-256 document hash as 64 lowercase hexadecimal characters",
57
+ }));
58
+ /**
59
+ * The document store with a first-insert hook: its `put` runs `hook` after
60
+ * a `put` that inserted (`inserted: true`), and never after one that found
61
+ * the document already stored, so the hook runs once per document (an
62
+ * embedding job, in the apps). The hook runs in the `put`'s own
63
+ * transaction, so a failure (the store's `E`) fails the `put` and rolls
64
+ * the insert back with the caller's transaction; enqueue durable work as a
65
+ * row the same transaction writes (an outbox). `get` and `getMany` are the
66
+ * store's own.
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * import { deepStrictEqual } from "node:assert";
71
+ * import * as Effect from "effect/Effect";
72
+ * import { onDocumentInsert } from "@thomasfosterau/pulpit/documents";
73
+ * import { makeInMemoryDocumentStore } from "@thomasfosterau/pulpit/testing";
74
+ *
75
+ * const inserted: Array<string> = [];
76
+ * const store = onDocumentInsert(makeInMemoryDocumentStore<{ readonly text: string }>(), (event) =>
77
+ * Effect.sync(() => {
78
+ * inserted.push(event.document.text);
79
+ * }),
80
+ * );
81
+ * await Effect.runPromise(Effect.andThen(store.put({ text: "hello" }), store.put({ text: "hello" })));
82
+ * deepStrictEqual(inserted, ["hello"]);
83
+ * ```
84
+ *
85
+ * @since 0.1.0
86
+ */
87
+ export const onDocumentInsert = (store, hook) => ({
88
+ ...store,
89
+ put: (document) => Effect.tap(store.put(document), ({ hash, inserted }) => inserted ? hook({ hash, document }) : Effect.void),
90
+ });
91
+ // ── The hash contract ─────────────────────────────────────────────────
92
+ const utf8 = new TextEncoder();
93
+ /** A string's UTF-8 bytes as one character per byte, so that string order is byte order. */
94
+ const byteString = (value) => String.fromCharCode(...utf8.encode(value));
95
+ /** jsonb's key order: shorter UTF-8 byte length first, then bytewise. */
96
+ const keyOrder = Order.combine(Order.mapInput(Order.Number, (key) => utf8.encode(key).length), Order.mapInput(Order.String, byteString));
97
+ /**
98
+ * A number as Postgres `numeric` prints it: JavaScript's shortest round-trip
99
+ * digits, written positionally (`1e+21` → `1000000000000000000000`,
100
+ * `1.5e-7` → `0.00000015`), since `numeric` has no exponent form.
101
+ */
102
+ const numericText = (value) => {
103
+ const shortest = String(value);
104
+ const match = /^(-?)(\d)(?:\.(\d+))?e([+-]\d+)$/.exec(shortest);
105
+ if (match === null)
106
+ return shortest;
107
+ const [, sign = "", lead = "", fraction = "", exponent = "0"] = match;
108
+ const digits = lead + fraction;
109
+ const point = 1 + Number(exponent);
110
+ if (point >= digits.length)
111
+ return sign + digits + "0".repeat(point - digits.length);
112
+ if (point <= 0)
113
+ return `${sign}0.${"0".repeat(-point)}${digits}`;
114
+ return `${sign}${digits.slice(0, point)}.${digits.slice(point)}`;
115
+ };
116
+ /** The short escapes jsonb's `escape_json` writes; every other control character is `\u00xx`. */
117
+ const SHORT_ESCAPES = {
118
+ '"': '\\"',
119
+ "\\": "\\\\",
120
+ "\b": "\\b",
121
+ "\f": "\\f",
122
+ "\n": "\\n",
123
+ "\r": "\\r",
124
+ "\t": "\\t",
125
+ };
126
+ /** One character as jsonb's `escape_json` writes it: a short escape, `\u00xx` below U+0020, else itself. */
127
+ const escapeCharacter = (character) => {
128
+ const code = character.charCodeAt(0);
129
+ return (SHORT_ESCAPES[character] ??
130
+ (code < 0x20 ? `\\u${code.toString(16).padStart(4, "0")}` : character));
131
+ };
132
+ /** A string literal as jsonb prints it: `"`, `\` and the control characters escaped, nothing else. */
133
+ const stringText = (value) => `"${Arr.map(Arr.fromIterable(value), escapeCharacter).join("")}"`;
134
+ /**
135
+ * The canonical text of a JSON document, the bytes the {@link DocumentStore}
136
+ * hash contract (README → Seams → `DocumentStore`) takes SHA-256 over:
137
+ * PostgreSQL's `jsonb` text output, with object keys ordered shorter-first by
138
+ * UTF-8 byte length and then bytewise, `": "` after a key, `", "` between
139
+ * members and elements, only `"`, `\` and the control characters escaped, and
140
+ * numbers as `numeric` prints them (never with an exponent). A Postgres store
141
+ * never needs it (the database derives the hash from `document_jsonb::text`);
142
+ * a store that doesn't run on Postgres hashes the UTF-8 bytes of this text to
143
+ * reproduce the contract's hashes. The kernel's suite pins it to text and
144
+ * hashes taken from Postgres. Pure and total.
145
+ *
146
+ * @example
147
+ * ```ts
148
+ * import { strictEqual } from "node:assert";
149
+ * import { canonicalText } from "@thomasfosterau/pulpit/documents";
150
+ *
151
+ * strictEqual(
152
+ * canonicalText({ b: 1e21, aa: [true, null], a: "é" }),
153
+ * '{"a": "é", "b": 1000000000000000000000, "aa": [true, null]}',
154
+ * );
155
+ * ```
156
+ *
157
+ * @since 0.1.0
158
+ */
159
+ export const canonicalText = (value) => {
160
+ if (value === null)
161
+ return "null";
162
+ if (Predicate.isBoolean(value))
163
+ return value ? "true" : "false";
164
+ if (Predicate.isNumber(value))
165
+ return numericText(value);
166
+ if (Predicate.isString(value))
167
+ return stringText(value);
168
+ if (Arr.isArray(value))
169
+ return `[${value.map(canonicalText).join(", ")}]`;
170
+ const members = Arr.sort(Record.toEntries(value), Order.mapInput(keyOrder, ([key]) => key));
171
+ return `{${members.map(([key, member]) => `${stringText(key)}: ${canonicalText(member)}`).join(", ")}}`;
172
+ };
173
+ // ── The field spread ──────────────────────────────────────────────────
174
+ /**
175
+ * The attribute spread for a version row's document reference: `key` carries
176
+ * the {@link DocumentHash} a {@link DocumentStore} returned from `put` (its `hash`).
177
+ * Required on the resource and at create; optional at update, where an absent
178
+ * key carries the previous version's reference forward (the tri-state rule),
179
+ * and never cleared. Spread into a leaf's `Resource.make({ attributes })`,
180
+ * once per document column (`...documentRef("title")`,
181
+ * `...documentRef("body")`).
182
+ *
183
+ * A document the leaf may omit is declared directly, as
184
+ * `Resource.attribute(Schema.NullOr(DocumentHash), { create: "optional" })`,
185
+ * so that `null` clears it.
186
+ *
187
+ * @since 0.1.0
188
+ */
189
+ export const documentRef = (key) => Record.singleton(key, Resource.attribute(DocumentHash));
190
+ //# sourceMappingURL=documents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documents.js","sourceRoot":"","sources":["../src/documents.ts"],"names":[],"mappings":"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"}