@arki/journal 0.0.0-stage → 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 (121) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +541 -2
  3. package/dist/api.d.ts +336 -0
  4. package/dist/api.d.ts.map +1 -0
  5. package/dist/api.js +2 -0
  6. package/dist/api.js.map +1 -0
  7. package/dist/command.d.ts +27 -0
  8. package/dist/command.d.ts.map +1 -0
  9. package/dist/command.js +157 -0
  10. package/dist/command.js.map +1 -0
  11. package/dist/debug.d.ts +7 -0
  12. package/dist/debug.d.ts.map +1 -0
  13. package/dist/debug.js +8 -0
  14. package/dist/debug.js.map +1 -0
  15. package/dist/dot.d.ts +102 -0
  16. package/dist/dot.d.ts.map +1 -0
  17. package/dist/dot.js +159 -0
  18. package/dist/dot.js.map +1 -0
  19. package/dist/entity.d.ts +56 -0
  20. package/dist/entity.d.ts.map +1 -0
  21. package/dist/entity.js +174 -0
  22. package/dist/entity.js.map +1 -0
  23. package/dist/errors.d.ts +47 -0
  24. package/dist/errors.d.ts.map +1 -0
  25. package/dist/errors.js +112 -0
  26. package/dist/errors.js.map +1 -0
  27. package/dist/event.d.ts +9 -0
  28. package/dist/event.d.ts.map +1 -0
  29. package/dist/event.js +19 -0
  30. package/dist/event.js.map +1 -0
  31. package/dist/fold.d.ts +29 -0
  32. package/dist/fold.d.ts.map +1 -0
  33. package/dist/fold.js +49 -0
  34. package/dist/fold.js.map +1 -0
  35. package/dist/handlers.d.ts +26 -0
  36. package/dist/handlers.d.ts.map +1 -0
  37. package/dist/handlers.js +130 -0
  38. package/dist/handlers.js.map +1 -0
  39. package/dist/history.d.ts +49 -0
  40. package/dist/history.d.ts.map +1 -0
  41. package/dist/history.js +259 -0
  42. package/dist/history.js.map +1 -0
  43. package/dist/hooks.d.ts +14 -0
  44. package/dist/hooks.d.ts.map +1 -0
  45. package/dist/hooks.js +67 -0
  46. package/dist/hooks.js.map +1 -0
  47. package/dist/id.d.ts +18 -0
  48. package/dist/id.d.ts.map +1 -0
  49. package/dist/id.js +41 -0
  50. package/dist/id.js.map +1 -0
  51. package/dist/index.d.ts +12 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +5 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/journal.d.ts +51 -0
  56. package/dist/journal.d.ts.map +1 -0
  57. package/dist/journal.js +181 -0
  58. package/dist/journal.js.map +1 -0
  59. package/dist/listen.d.ts +37 -0
  60. package/dist/listen.d.ts.map +1 -0
  61. package/dist/listen.js +90 -0
  62. package/dist/listen.js.map +1 -0
  63. package/dist/orpc.d.ts +21 -0
  64. package/dist/orpc.d.ts.map +1 -0
  65. package/dist/orpc.js +57 -0
  66. package/dist/orpc.js.map +1 -0
  67. package/dist/pg.d.ts +537 -0
  68. package/dist/pg.d.ts.map +1 -0
  69. package/dist/pg.js +80 -0
  70. package/dist/pg.js.map +1 -0
  71. package/dist/storage.d.ts +52 -0
  72. package/dist/storage.d.ts.map +1 -0
  73. package/dist/storage.js +221 -0
  74. package/dist/storage.js.map +1 -0
  75. package/dist/testing.d.ts +19 -0
  76. package/dist/testing.d.ts.map +1 -0
  77. package/dist/testing.js +23 -0
  78. package/dist/testing.js.map +1 -0
  79. package/dist/transaction-guard.d.ts +2 -0
  80. package/dist/transaction-guard.d.ts.map +1 -0
  81. package/dist/transaction-guard.js +39 -0
  82. package/dist/transaction-guard.js.map +1 -0
  83. package/dist/types.d.ts +414 -0
  84. package/dist/types.d.ts.map +1 -0
  85. package/dist/types.js +2 -0
  86. package/dist/types.js.map +1 -0
  87. package/dist/worker.d.ts +109 -0
  88. package/dist/worker.d.ts.map +1 -0
  89. package/dist/worker.js +459 -0
  90. package/dist/worker.js.map +1 -0
  91. package/dist/write.d.ts +39 -0
  92. package/dist/write.d.ts.map +1 -0
  93. package/dist/write.js +287 -0
  94. package/dist/write.js.map +1 -0
  95. package/docs/agent-guide.md +324 -0
  96. package/llms.txt +40 -0
  97. package/package.json +99 -4
  98. package/src/api.ts +465 -0
  99. package/src/command.ts +242 -0
  100. package/src/debug.ts +8 -0
  101. package/src/dot.ts +296 -0
  102. package/src/entity.ts +377 -0
  103. package/src/errors.ts +134 -0
  104. package/src/event.ts +26 -0
  105. package/src/fold.ts +97 -0
  106. package/src/handlers.ts +262 -0
  107. package/src/history.ts +390 -0
  108. package/src/hooks.ts +85 -0
  109. package/src/id.ts +54 -0
  110. package/src/index.ts +76 -0
  111. package/src/journal.ts +256 -0
  112. package/src/listen.ts +109 -0
  113. package/src/orpc.ts +59 -0
  114. package/src/pg.ts +107 -0
  115. package/src/storage.ts +277 -0
  116. package/src/test-support.ts +82 -0
  117. package/src/testing.ts +23 -0
  118. package/src/transaction-guard.ts +41 -0
  119. package/src/types.ts +439 -0
  120. package/src/worker.ts +610 -0
  121. package/src/write.ts +434 -0
package/dist/dot.d.ts ADDED
@@ -0,0 +1,102 @@
1
+ /**
2
+ * `@arki/dot` adapter.
3
+ *
4
+ * - `journal(...)` is the plugin. It publishes the journal as `services.journal`
5
+ * and runs one worker per plugin instance between `start` and `stop`.
6
+ * - `journalHandlers(...)` is the feature slice. A feature lists its projections
7
+ * and effects with it, and `journal({ j, tables, features })` collects them.
8
+ *
9
+ * The app's `db` plugin must publish the root Drizzle database under the `db` key.
10
+ *
11
+ * `@arki/dot` is an optional peer. Import this module only inside a Dot app.
12
+ * Feature slices are read by shape: this file never imports `@arki/feature`.
13
+ */
14
+ import type { z } from 'zod';
15
+ import type { Plugin, Token } from '@arki/dot/plugin';
16
+ import type { Journal, JournalDefinitions } from './api.js';
17
+ import type { JournalTables } from './pg.js';
18
+ import type { DbHandle, Effect, Needs, Projection, RootDb, ServicesFor, Tap, TransactionSource, WakeListener } from './types.js';
19
+ export declare const JOURNAL_ACTION_META_SCHEMA = "@arki/journal/action-meta@1";
20
+ export declare const JOURNAL_PLUGIN_ERROR_CODES: {
21
+ readonly badSlice: 'JOURNAL_PLUGIN_E001';
22
+ };
23
+ /** One effect in the app manifest: async work that the journal's job table feeds in. */
24
+ export type JournalDotAction = {
25
+ readonly id: string;
26
+ readonly binding: 'journal';
27
+ readonly direction: 'in';
28
+ readonly address: string;
29
+ readonly metaSchema: typeof JOURNAL_ACTION_META_SCHEMA;
30
+ readonly meta: {
31
+ readonly entities: string[];
32
+ readonly events: string[];
33
+ };
34
+ };
35
+ /** What one feature hands to the journal. Its effects already hold the feature's services. */
36
+ export type JournalHandlers<Meta = unknown, Tx = DbHandle, AppDb = RootDb> = {
37
+ readonly projections: readonly Projection<Meta, Tx>[];
38
+ readonly effects: readonly Effect<Meta, AppDb>[];
39
+ readonly taps: readonly Tap<Meta, AppDb>[];
40
+ };
41
+ /**
42
+ * The `@arki/feature` slice contract a `journalHandlers(...)` declaration produces.
43
+ * `TServices` is what the feature's effects need: the feature must declare it in `needs`.
44
+ */
45
+ export type JournalFeatureSlice<Meta = unknown, Tx = DbHandle, AppDb = RootDb, TServices = unknown> = {
46
+ readonly key: 'journal';
47
+ readonly actions: readonly JournalDotAction[];
48
+ readonly resolve: (services: TServices) => JournalHandlers<Meta, Tx, AppDb>;
49
+ };
50
+ /**
51
+ * Declare a feature's projections and effects as a boot-time slice
52
+ * (`defineFeature(..., { use: [journalHandlers({ projections, effects })] })`).
53
+ * The payload lands under `journal` in the feature's slice token, where
54
+ * `journal({ j, tables, features })` collects it.
55
+ *
56
+ * An effect with needs (`j.effect(name, entity, { mailer: need<Mailer>() }, on)`) gets
57
+ * them from the booted services of this feature, by name. The feature must declare
58
+ * each one in its own `needs`: the slice's `resolve` does not type-check otherwise.
59
+ * Two features may use the same need name for two different services.
60
+ *
61
+ * Actions: one per effect. Projections add none: they run inside the write, not
62
+ * at an app boundary.
63
+ */
64
+ export declare function journalHandlers<Meta = unknown, Tx = DbHandle, AppDb = RootDb, const E extends readonly Effect<Meta, AppDb, unknown, Needs>[] = readonly [], const T extends readonly Tap<Meta, AppDb, Needs>[] = readonly []>(handlers: {
65
+ readonly projections?: readonly Projection<Meta, Tx>[];
66
+ readonly effects?: E;
67
+ readonly taps?: T;
68
+ }): JournalFeatureSlice<Meta, Tx, AppDb, ServicesFor<E> & ServicesFor<T>>;
69
+ type WorkerOptions = {
70
+ /** Run the async lane in this process. Default `true`. */
71
+ readonly worker?: boolean;
72
+ readonly pollMs?: number;
73
+ readonly batchSize?: number;
74
+ readonly leaseSeconds?: number;
75
+ /** See `journal.worker`: required when the journal has taps, unless `taps: false`. */
76
+ readonly listen?: WakeListener;
77
+ readonly taps?: boolean;
78
+ };
79
+ /** Direct mode: the app composed the journal itself. */
80
+ export type JournalDirectOptions<Meta extends z.ZodObject, Tx extends TransactionSource<Tx>, AppDb extends RootDb> = WorkerOptions & {
81
+ readonly journal: Journal<Meta, Tx, AppDb>;
82
+ };
83
+ /** Feature mode: the plugin composes the journal from the features' `journalHandlers` slices. */
84
+ export type JournalFeatureOptions<Meta extends z.ZodObject, Tx extends TransactionSource<Tx>, AppDb extends RootDb> = WorkerOptions & {
85
+ readonly j: Pick<JournalDefinitions<Meta, Tx, AppDb>, 'compose'>;
86
+ readonly tables: JournalTables;
87
+ /** `tokens(features)` from `@arki/feature/dot`. A feature without a journal slice adds nothing. */
88
+ readonly features: readonly Token<object, string>[];
89
+ };
90
+ /**
91
+ * `RootDb` again on purpose: a Drizzle transaction fits `AppDb` by shape, and must not fit here.
92
+ * `TransactionSource<Tx>`: the worker's database runs the commands of effects, so it opens `Tx`.
93
+ */
94
+ export type JournalPlugin<Meta extends z.ZodObject, Tx extends TransactionSource<Tx>, AppDb extends RootDb> = Plugin<{
95
+ db: AppDb & RootDb & TransactionSource<Tx>;
96
+ }, {
97
+ journal: Journal<Meta, Tx, AppDb>;
98
+ }>;
99
+ export declare function journal<Meta extends z.ZodObject, Tx extends TransactionSource<Tx>, AppDb extends RootDb>(options: JournalDirectOptions<Meta, Tx, AppDb>): JournalPlugin<Meta, Tx, AppDb>;
100
+ export declare function journal<Meta extends z.ZodObject, Tx extends TransactionSource<Tx>, AppDb extends RootDb>(options: JournalFeatureOptions<Meta, Tx, AppDb>): JournalPlugin<Meta, Tx, AppDb>;
101
+ export {};
102
+ //# sourceMappingURL=dot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dot.d.ts","sourceRoot":"","sources":["../src/dot.ts"],"names":[],"mappings":"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"}
package/dist/dot.js ADDED
@@ -0,0 +1,159 @@
1
+ import { DotPluginError, plugin, service } from '@arki/dot/plugin';
2
+ import { debugWorker } from './debug.js';
3
+ import { JournalError } from './errors.js';
4
+ import { bindEffect, bindTap } from './handlers.js';
5
+ export const JOURNAL_ACTION_META_SCHEMA = '@arki/journal/action-meta@1';
6
+ export const JOURNAL_PLUGIN_ERROR_CODES = { badSlice: 'JOURNAL_PLUGIN_E001' };
7
+ function effectAction(effect) {
8
+ return {
9
+ id: `${effect.name}.run`,
10
+ binding: 'journal',
11
+ direction: 'in',
12
+ address: effect.name,
13
+ metaSchema: JOURNAL_ACTION_META_SCHEMA,
14
+ meta: { entities: effect.entities.map(entity => entity.kind), events: [...effect.types] },
15
+ };
16
+ }
17
+ /**
18
+ * Declare a feature's projections and effects as a boot-time slice
19
+ * (`defineFeature(..., { use: [journalHandlers({ projections, effects })] })`).
20
+ * The payload lands under `journal` in the feature's slice token, where
21
+ * `journal({ j, tables, features })` collects it.
22
+ *
23
+ * An effect with needs (`j.effect(name, entity, { mailer: need<Mailer>() }, on)`) gets
24
+ * them from the booted services of this feature, by name. The feature must declare
25
+ * each one in its own `needs`: the slice's `resolve` does not type-check otherwise.
26
+ * Two features may use the same need name for two different services.
27
+ *
28
+ * Actions: one per effect. Projections add none: they run inside the write, not
29
+ * at an app boundary.
30
+ */
31
+ export function journalHandlers(handlers) {
32
+ const { projections = [], effects = [], taps = [] } = handlers;
33
+ return {
34
+ key: 'journal',
35
+ actions: effects.map(effect => effectAction(effect)),
36
+ // The kernel hands the feature's services over untyped, so they are read by name here.
37
+ resolve: (services) => ({
38
+ projections,
39
+ effects: effects.map(effect => bindEffect(effect, isServices(services) ? services : {})),
40
+ taps: taps.map(tap => bindTap(tap, isServices(services) ? services : {})),
41
+ }),
42
+ };
43
+ }
44
+ const isServices = (value) => typeof value === 'object' && value !== null;
45
+ const isHandler = (value) => typeof value === 'object' &&
46
+ value !== null &&
47
+ 'name' in value &&
48
+ typeof value.name === 'string' &&
49
+ 'run' in value &&
50
+ typeof value.run === 'function';
51
+ /** The kernel hands services over untyped, so the slice shape is checked here, once, at boot. */
52
+ function isJournalHandlers(value) {
53
+ return (typeof value === 'object' &&
54
+ value !== null &&
55
+ 'projections' in value &&
56
+ Array.isArray(value.projections) &&
57
+ value.projections.every(item => isHandler(item)) &&
58
+ 'effects' in value &&
59
+ Array.isArray(value.effects) &&
60
+ value.effects.every(item => isHandler(item)) &&
61
+ 'taps' in value &&
62
+ Array.isArray(value.taps) &&
63
+ value.taps.every(item => isHandler(item)));
64
+ }
65
+ /**
66
+ * Throws when another handler of this kind already has the name, and names the
67
+ * feature tokens that hold the two. `j.compose` makes the same check, but it gets
68
+ * one flat list and cannot say which feature a handler came from.
69
+ */
70
+ function claimName(kind, owners, name, feature) {
71
+ const owner = owners.get(name);
72
+ if (owner !== undefined) {
73
+ const where = owner === feature ? `feature "${feature}", twice` : `features "${owner}" and "${feature}"`;
74
+ throw new JournalError('BAD_DEFINITION', kind, name, `journal: duplicate ${kind} name "${name}" (${where})`);
75
+ }
76
+ owners.set(name, feature);
77
+ }
78
+ /**
79
+ * Read each feature's `journal` slice from the booted services, in feature order.
80
+ * Refuses a projection name or an effect name that is listed twice.
81
+ */
82
+ function collectHandlers(features, services) {
83
+ const projections = [];
84
+ const effects = [];
85
+ const taps = [];
86
+ const projectionOwners = new Map();
87
+ const effectOwners = new Map();
88
+ const tapOwners = new Map();
89
+ for (const feature of features) {
90
+ const slices = services[feature.key];
91
+ const slice = typeof slices === 'object' && slices !== null && 'journal' in slices ? slices.journal : undefined;
92
+ if (slice === undefined) {
93
+ debugWorker.debug('[journal] feature "{featureKey}" did not contribute a journal slice', {
94
+ featureKey: feature.key,
95
+ });
96
+ continue;
97
+ }
98
+ if (!isJournalHandlers(slice)) {
99
+ throw new DotPluginError({
100
+ code: JOURNAL_PLUGIN_ERROR_CODES.badSlice,
101
+ message: `[journal] feature token "${feature.key}" holds a \`journal\` slice that is not lists of projections, effects and taps.`,
102
+ remediation: 'Declare the slice with journalHandlers({ projections, effects, taps }) from @arki/journal/dot.',
103
+ docsUrl: 'https://github.com/arki-labs/arki/blob/HEAD/packages/journal/docs/agent-guide.md#dot-boot-errors',
104
+ });
105
+ }
106
+ for (const { name } of slice.projections)
107
+ claimName('projection', projectionOwners, name, feature.key);
108
+ for (const { name } of slice.effects)
109
+ claimName('effect', effectOwners, name, feature.key);
110
+ for (const { name } of slice.taps)
111
+ claimName('tap', tapOwners, name, feature.key);
112
+ projections.push(...slice.projections);
113
+ effects.push(...slice.effects);
114
+ taps.push(...slice.taps);
115
+ }
116
+ return { projections, effects, taps };
117
+ }
118
+ export function journal(options) {
119
+ let composed;
120
+ let worker;
121
+ // Each feature token is a need, so the kernel boots the features first and refuses a missing one.
122
+ const featureNeeds = 'features' in options ? options.features.map(feature => [feature.key, feature]) : [];
123
+ const needs = {
124
+ ...Object.fromEntries(featureNeeds),
125
+ db: service(),
126
+ };
127
+ return plugin({
128
+ name: 'journal',
129
+ needs,
130
+ configure(ctx) {
131
+ ctx.registerService('journal', 'custom');
132
+ ctx.declareProvides('journal');
133
+ },
134
+ boot(services) {
135
+ composed =
136
+ 'journal' in options
137
+ ? options.journal
138
+ : options.j.compose({ tables: options.tables, ...collectHandlers(options.features, services) });
139
+ return { journal: composed };
140
+ },
141
+ async start({ db }) {
142
+ if (options.worker === false)
143
+ return;
144
+ worker = composed?.worker(db, {
145
+ pollMs: options.pollMs,
146
+ batchSize: options.batchSize,
147
+ leaseSeconds: options.leaseSeconds,
148
+ listen: options.listen,
149
+ taps: options.taps,
150
+ });
151
+ await worker?.start();
152
+ },
153
+ async stop() {
154
+ await worker?.stop();
155
+ worker = undefined;
156
+ },
157
+ });
158
+ }
159
+ //# sourceMappingURL=dot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dot.js","sourceRoot":"","sources":["../src/dot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import type { AnyEventDef, EntityDef, EntityId, EntityTable, Patch, Recorded, RecordedOf, ReducerRow, ReducerValues, Row } from './types.js';
2
+ /**
3
+ * The keys that every `.create` reducer so far returns with a type that
4
+ * excludes `undefined`. `CreateResults` is the union of their return types, so a key
5
+ * one of them leaves out, or may set to `undefined`, is not in the set. With
6
+ * no `.create` yet, the set is empty.
7
+ *
8
+ * `keyof CreateResults & string` keeps the mapped type from distributing over the
9
+ * union: `keyof (A | B)` is the keys both have, and `CreateResults[P]` is the union
10
+ * of what each branch holds there. A create returning `cond ? { note } : {}`
11
+ * therefore promises nothing.
12
+ */
13
+ export type GuaranteedCreateKeys<CreateResults> = [CreateResults] extends [never] ? never : {
14
+ [P in keyof CreateResults & string]: undefined extends CreateResults[P] ? never : P;
15
+ }[keyof CreateResults & string];
16
+ /**
17
+ * The state an `.on` reducer receives: the reducer-shaped row, with the keys
18
+ * every `.create` reducer sets made required. A patch cannot unset a key and
19
+ * `undefined` values are stripped, so a key a create sets stays set.
20
+ */
21
+ export type OnReducerRow<Tbl extends EntityTable, Key extends string, CreateResults> = ReducerRow<Tbl, Key> & Required<Pick<ReducerValues<Tbl, Key>, GuaranteedCreateKeys<CreateResults> & keyof ReducerValues<Tbl, Key>>>;
22
+ /**
23
+ * An entity under construction. Each `.create` / `.on` adds the event to the
24
+ * union the entity accepts, so `events` cannot return an undeclared event.
25
+ * `CreateResults` collects what the `.create` reducers return; see `OnReducerRow`.
26
+ *
27
+ * The rule: every `.create` comes before the first `.on`. After the first
28
+ * `.on` the builder is `OnOnlyEntityBuilder`, which has no `.create`: a later
29
+ * create could leave out a column that `.on` was promised.
30
+ */
31
+ export type EntityBuilder<Kind extends string, Tbl extends EntityTable, EventDefs extends AnyEventDef, Meta, Key extends string = 'id', CreateResults = never> = OnOnlyEntityBuilder<Kind, Tbl, EventDefs, Meta, Key, CreateResults> & {
32
+ /** A create reducer receives no row. It builds the first row from the event. */
33
+ create<AddedEvent extends AnyEventDef, CreateResult extends ReducerValues<Tbl, Key>>(event: AddedEvent, reduce: (event: RecordedOf<AddedEvent, Meta, EntityId<Tbl, Key>>) => CreateResult): EntityBuilder<Kind, Tbl, EventDefs | AddedEvent, Meta, Key, CreateResults | CreateResult>;
34
+ };
35
+ /** An entity after its first `.on`: only more `.on` reducers may follow. */
36
+ export type OnOnlyEntityBuilder<Kind extends string, Tbl extends EntityTable, EventDefs extends AnyEventDef, Meta, Key extends string = 'id', CreateResults = never> = EntityDef<Kind, Tbl, EventDefs, Meta, Key> & {
37
+ /**
38
+ * An `.on` reducer always receives the reducer-shaped state: the insert shape plus
39
+ * the key column and `version`. Columns with database defaults may be absent unless
40
+ * every `.create` reducer sets them. It returns a patch that is merged onto the
41
+ * state. Returning `null` deletes the row; the events stay.
42
+ */
43
+ on<AddedEvent extends AnyEventDef>(event: AddedEvent, reduce: (row: OnReducerRow<Tbl, Key, CreateResults>, event: RecordedOf<AddedEvent, Meta, EntityId<Tbl, Key>>) => Patch<Tbl, Key> | null): OnOnlyEntityBuilder<Kind, Tbl, EventDefs | AddedEvent, Meta, Key, CreateResults>;
44
+ };
45
+ export declare function defineEntity<Kind extends string, Tbl extends EntityTable, Meta, Key extends string>(kind: Kind, table: Tbl, key: Key): EntityBuilder<Kind, Tbl, never, Meta, Key>;
46
+ /** The reducer-shaped row: a state plus the key column and `version`. */
47
+ export declare function withEntityIdentity<Tbl extends EntityTable, Key extends string>(entity: EntityDef<string, Tbl, AnyEventDef, unknown, Key>, state: Row<Tbl> | ReducerValues<Tbl, Key>, id: EntityId<Tbl, Key>, version: number): ReducerRow<Tbl, Key>;
48
+ /**
49
+ * Fold one event onto the in-memory state. Shared by write and replay, so
50
+ * both produce the same value for every column a reducer set. The caller has
51
+ * checked that a reducer exists for the event in this state. `reducerColumns`, when
52
+ * given, collects the columns the reducers set: a create starts it afresh, a
53
+ * patch adds its keys, a delete empties it.
54
+ */
55
+ export declare function reduceOne<Tbl extends EntityTable, Meta, Key extends string>(entity: EntityDef<string, Tbl, AnyEventDef, Meta, Key>, state: Row<Tbl> | ReducerValues<Tbl, Key> | null, id: EntityId<Tbl, Key>, event: Recorded<string, unknown, Meta>, reducerColumns?: Set<string>): ReducerValues<Tbl, Key> | null;
56
+ //# sourceMappingURL=entity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../src/entity.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,WAAW,EACX,SAAS,EACT,QAAQ,EACR,WAAW,EAGX,KAAK,EACL,QAAQ,EACR,UAAU,EACV,UAAU,EAEV,aAAa,EACb,GAAG,EACJ,MAAM,YAAY,CAAC;AAIpB;;;;;;;;;;GAUG;AACH,MAAM,MAAM,oBAAoB,CAAC,aAAa,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,KAAK,CAAC,GAC7E,KAAK,GACL;KAAG,CAAC,IAAI,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,SAAS,aAAa,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC;CAAE,CAAC,MAAM,aAAa,GACzG,MAAM,CAAC,CAAC;AAEd;;;;GAIG;AACH,MAAM,MAAM,YAAY,CAAC,GAAG,SAAS,WAAW,EAAE,GAAG,SAAS,MAAM,EAAE,aAAa,IAAI,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,GACzG,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,oBAAoB,CAAC,aAAa,CAAC,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;AAE/G;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,CACvB,IAAI,SAAS,MAAM,EACnB,GAAG,SAAS,WAAW,EACvB,SAAS,SAAS,WAAW,EAC7B,IAAI,EACJ,GAAG,SAAS,MAAM,GAAG,IAAI,EACzB,aAAa,GAAG,KAAK,IACnB,mBAAmB,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,aAAa,CAAC,GAAG;IACxE,gFAAgF;IAChF,MAAM,CAAC,UAAU,SAAS,WAAW,EAAE,YAAY,SAAS,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,EACjF,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,UAAU,EAAE,IAAI,EAAE,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,YAAY,GAChF,aAAa,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,GAAG,UAAU,EAAE,IAAI,EAAE,GAAG,EAAE,aAAa,GAAG,YAAY,CAAC,CAAC;CAC9F,CAAC;AAEF,4EAA4E;AAC5E,MAAM,MAAM,mBAAmB,CAC7B,IAAI,SAAS,MAAM,EACnB,GAAG,SAAS,WAAW,EACvB,SAAS,SAAS,WAAW,EAC7B,IAAI,EACJ,GAAG,SAAS,MAAM,GAAG,IAAI,EACzB,aAAa,GAAG,KAAK,IACnB,SAAS,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG;IAC/C;;;;;OAKG;IACH,EAAE,CAAC,UAAU,SAAS,WAAW,EAC/B,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,CACN,GAAG,EAAE,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,aAAa,CAAC,EAC1C,KAAK,EAAE,UAAU,CAAC,UAAU,EAAE,IAAI,EAAE,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KACpD,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,IAAI,GAC1B,mBAAmB,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,GAAG,UAAU,EAAE,IAAI,EAAE,GAAG,EAAE,aAAa,CAAC,CAAC;CACrF,CAAC;AAkHF,wBAAgB,YAAY,CAAC,IAAI,SAAS,MAAM,EAAE,GAAG,SAAS,WAAW,EAAE,IAAI,EAAE,GAAG,SAAS,MAAM,EACjG,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,GAAG,EACV,GAAG,EAAE,GAAG,GACP,aAAa,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,CAgB5C;AA4GD,yEAAyE;AACzE,wBAAgB,kBAAkB,CAAC,GAAG,SAAS,WAAW,EAAE,GAAG,SAAS,MAAM,EAC5E,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,CAAC,EACzD,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,EACzC,EAAE,EAAE,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,EACtB,OAAO,EAAE,MAAM,GACd,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAEtB;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,GAAG,SAAS,WAAW,EAAE,IAAI,EAAE,GAAG,SAAS,MAAM,EACzE,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,GAAG,CAAC,EACtD,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,IAAI,EAChD,EAAE,EAAE,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,EACtB,KAAK,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EACtC,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAC3B,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,IAAI,CAoBhC"}
package/dist/entity.js ADDED
@@ -0,0 +1,174 @@
1
+ import { getTableColumns, getTableName } from 'drizzle-orm';
2
+ import { getTableConfig, PgColumn } from 'drizzle-orm/pg-core';
3
+ import { JournalError } from './errors.js';
4
+ import { assertStoredName } from './id.js';
5
+ /** The two facts `KeyColumn` states: the column holds a string and is not null. */
6
+ function isKeyColumn(column) {
7
+ const [dataType] = column.dataType.split(' ');
8
+ return dataType === 'string' && column.notNull;
9
+ }
10
+ /**
11
+ * The supported key types: `text`, `varchar`, `varchar(n)` and `uuid`. The first three compare
12
+ * as exact strings. A `uuid` ignores case, so its ids must also use the canonical spelling
13
+ * (lower-case, hyphenated); `assertExactId` checks that on every call.
14
+ */
15
+ const EXACT_KEY_TYPE = /^(?:text|uuid|varchar(?:\s*\(\s*\d+\s*\))?)$/;
16
+ /** A column's SQL type in comparable form: trimmed and lower-case. */
17
+ function sqlTypeOf(column) {
18
+ return column.getSQLType().trim().toLowerCase();
19
+ }
20
+ /** The journal's own limit: `journal_events.entity_id` holds 256 characters. */
21
+ const LOG_KEY_LIMIT = 256;
22
+ /**
23
+ * The longest id the key may hold, in code points: `varchar(n)` allows `min(n, 256)`, `text` and
24
+ * `varchar` allow the log's 256, and a `uuid` is fixed at 36.
25
+ */
26
+ function keyLimitOf(column) {
27
+ const sqlType = sqlTypeOf(column);
28
+ if (sqlType === 'uuid')
29
+ return { max: 36, source: 'column' };
30
+ const length = /\(\s*(\d+)\s*\)/.exec(sqlType)?.[1];
31
+ if (length !== undefined && Number(length) <= LOG_KEY_LIMIT)
32
+ return { max: Number(length), source: 'column' };
33
+ return { max: LOG_KEY_LIMIT, source: 'journal' };
34
+ }
35
+ /**
36
+ * Whether `column` identifies one row on its own: it is the primary key, or
37
+ * carries a unique constraint, or is the only column of a table-level primary
38
+ * key, unique constraint or unique index. Table-level constraints and indexes
39
+ * hold their own column objects (an index wraps them), so they are matched by
40
+ * SQL name; an SQL expression in an index has no name and never matches.
41
+ */
42
+ function isUniqueOn(table, column) {
43
+ if (column.primary || column.isUnique)
44
+ return true;
45
+ const only = (columns) => {
46
+ const [first] = columns;
47
+ return (columns.length === 1 &&
48
+ typeof first === 'object' &&
49
+ first !== null &&
50
+ 'name' in first &&
51
+ first.name === column.name);
52
+ };
53
+ const config = getTableConfig(table);
54
+ return (config.primaryKeys.some(pk => only(pk.columns)) ||
55
+ config.uniqueConstraints.some(constraint => only(constraint.columns)) ||
56
+ // A unique index with a `where` is unique only inside that subset, so it is no key.
57
+ config.indexes.some(index => index.config.unique && index.config.where === undefined && only(index.config.columns)));
58
+ }
59
+ /** The column `table` holds under `key`, checked to be a usable entity key. */
60
+ function keyColumnOf(kind, table, key) {
61
+ const columns = getTableColumns(table);
62
+ const column = columns[key];
63
+ if (!(column instanceof PgColumn)) {
64
+ throw new JournalError('BAD_DEFINITION', 'entity', kind, `journal: entity "${kind}": table "${getTableName(table)}" has no column "${key}"`);
65
+ }
66
+ const [dataType] = column.dataType.split(' ');
67
+ const sqlType = sqlTypeOf(column);
68
+ if (dataType !== 'custom' && !isKeyColumn(column)) {
69
+ throw new JournalError('BAD_DEFINITION', 'entity', kind, dataType === 'string'
70
+ ? `journal: entity "${kind}": key column "${key}" must be not null`
71
+ : `journal: entity "${kind}": key column "${key}" must hold a string, it holds ${column.dataType}`);
72
+ }
73
+ if (!EXACT_KEY_TYPE.test(sqlType)) {
74
+ throw new JournalError('BAD_DEFINITION', 'entity', kind, `journal: entity "${kind}": key column "${key}" has SQL type "${sqlType}"; the key must be text, varchar or uuid, because other types can treat two different strings as the same key`);
75
+ }
76
+ if (!isKeyColumn(column)) {
77
+ throw new JournalError('BAD_DEFINITION', 'entity', kind, `journal: entity "${kind}": key column "${key}" is a custom column type; a key must be a plain text, varchar or uuid column, because a custom type's mapping can change the string`);
78
+ }
79
+ if (!isUniqueOn(table, column)) {
80
+ throw new JournalError('BAD_DEFINITION', 'entity', kind, `journal: entity "${kind}": key column "${key}" must be the primary key or carry a unique constraint`);
81
+ }
82
+ return column;
83
+ }
84
+ export function defineEntity(kind, table, key) {
85
+ assertStoredName('entity kind', kind);
86
+ const keyColumn = keyColumnOf(kind, table, key);
87
+ const uuidKey = sqlTypeOf(keyColumn) === 'uuid';
88
+ const keyLimit = keyLimitOf(keyColumn);
89
+ return builder(kind, table, key, keyColumn, uuidKey, keyLimit, new Map(), new Map(), null);
90
+ }
91
+ function builder(kind, table, key, keyColumn, uuidKey, keyLimit, events, reducers,
92
+ /** The first `.on` event type, once one is declared. `.create` is refused after it. */
93
+ firstOn) {
94
+ const declare = (event, next, sealedBy) => builder(kind, table, key, keyColumn, uuidKey, keyLimit, new Map([...events, [event.type, event]]), new Map([...reducers, [event.type, next]]), sealedBy);
95
+ const create = (event, reduce) => {
96
+ if (firstOn !== null) {
97
+ throw new JournalError('BAD_DEFINITION', 'entity', kind, `journal: entity "${kind}": declare .create(${event.type}) before any .on; .on(${firstOn}) came first`);
98
+ }
99
+ const current = reducers.get(event.type) ?? {};
100
+ if (current.create !== undefined) {
101
+ throw new JournalError('BAD_DEFINITION', 'entity', kind, `journal: entity "${kind}" already declares a create reducer for "${event.type}"`);
102
+ }
103
+ return declare(event, { ...current, create: reduce }, null);
104
+ };
105
+ const on = (event, reduce) => {
106
+ const current = reducers.get(event.type) ?? {};
107
+ if (current.on !== undefined) {
108
+ throw new JournalError('BAD_DEFINITION', 'entity', kind, `journal: entity "${kind}" already declares an on reducer for "${event.type}"`);
109
+ }
110
+ return declare(event, { ...current, on: reduce }, firstOn ?? event.type);
111
+ };
112
+ return { kind, table, key, keyColumn, uuidKey, keyLimit, events, reducers, create, on };
113
+ }
114
+ /**
115
+ * A copy of `value` without the own keys that hold `undefined`. In a patch,
116
+ * `undefined` means "unchanged". In a create result it means "not set": the
117
+ * database default applies, and the key is not part of the journal's state.
118
+ */
119
+ function definedOnly(value) {
120
+ const out = { ...value };
121
+ for (const key in out) {
122
+ if (Object.hasOwn(out, key) && out[key] === undefined)
123
+ delete out[key];
124
+ }
125
+ return out;
126
+ }
127
+ function hasKey(value, key) {
128
+ return key in value;
129
+ }
130
+ /**
131
+ * `{ [key]: id }` typed by the key. TypeScript types a computed key as an
132
+ * index signature; the guard above narrows it back to the one key.
133
+ */
134
+ function keyed(key, id) {
135
+ const out = { [key]: id };
136
+ if (!hasKey(out, key))
137
+ throw new Error(`journal: key "${key}" was not set`);
138
+ return out;
139
+ }
140
+ /** The reducer-shaped row: a state plus the key column and `version`. */
141
+ export function withEntityIdentity(entity, state, id, version) {
142
+ return { ...state, ...keyed(entity.key, id), version };
143
+ }
144
+ /**
145
+ * Fold one event onto the in-memory state. Shared by write and replay, so
146
+ * both produce the same value for every column a reducer set. The caller has
147
+ * checked that a reducer exists for the event in this state. `reducerColumns`, when
148
+ * given, collects the columns the reducers set: a create starts it afresh, a
149
+ * patch adds its keys, a delete empties it.
150
+ */
151
+ export function reduceOne(entity, state, id, event, reducerColumns) {
152
+ const reducers = entity.reducers.get(event.type);
153
+ if (state === null) {
154
+ if (reducers?.create === undefined)
155
+ throw new Error(`journal: entity "${entity.kind}" has no create reducer for "${event.type}"`);
156
+ const created = definedOnly(reducers.create(event));
157
+ reducerColumns?.clear();
158
+ for (const key of Object.keys(created))
159
+ reducerColumns?.add(key);
160
+ return created;
161
+ }
162
+ if (reducers?.on === undefined)
163
+ throw new Error(`journal: entity "${entity.kind}" has no on reducer for "${event.type}"`);
164
+ const patch = reducers.on(withEntityIdentity(entity, state, id, event.version - 1), event);
165
+ if (patch === null) {
166
+ reducerColumns?.clear();
167
+ return null;
168
+ }
169
+ const defined = definedOnly(patch);
170
+ for (const key of Object.keys(defined))
171
+ reducerColumns?.add(key);
172
+ return { ...state, ...defined };
173
+ }
174
+ //# sourceMappingURL=entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity.js","sourceRoot":"","sources":["../src/entity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ export type JournalErrorCode = 'CONFLICT' | 'NO_ROW' | 'ROW_EXISTS' | 'UNKNOWN_EVENT' | 'VERSION_MISMATCH' | 'NO_HISTORY' | 'HISTORY_GAP' | 'REPLAY_FAILED' | 'UNSTABLE_SCHEMA' | 'ID_MISMATCH' | 'VALUE_MISMATCH' | 'BAD_DEFINITION';
2
+ /**
3
+ * An error about one entity's row or history: a write, replay, verify or repair
4
+ * that the journal refuses. Match on `code`, never on the message.
5
+ *
6
+ * Not every error the journal lets out is a `JournalError`:
7
+ * - A wrong definition is a `JournalError` with code `BAD_DEFINITION`, thrown when the
8
+ * declaration runs (a bad name, a bad key column, a duplicate reducer or handler name).
9
+ * - A misuse at run time is a plain `Error` whose message starts with `journal:` (two
10
+ * operations at once on one transaction, `drain` inside a transaction).
11
+ * - Command input, event data or `meta` that fails its schema is a `ZodError`.
12
+ * - An error thrown by a command handler, an `events` function, a reducer or a
13
+ * projection passes through unchanged. A rejected business rule is one of
14
+ * these: a `CommandError`, below.
15
+ * An error thrown by an effect is caught by `drain` and stored on the job.
16
+ */
17
+ export declare class JournalError extends Error {
18
+ readonly code: JournalErrorCode;
19
+ readonly kind: string;
20
+ readonly entityId: string;
21
+ /** The message reads: `CODE: what happened (kind id): detail. What to do next.` */
22
+ constructor(code: JournalErrorCode, kind: string, entityId: string, detail?: string, options?: ErrorOptions);
23
+ }
24
+ export declare function isJournalError(error: unknown, code?: JournalErrorCode): error is JournalError;
25
+ /** The kind of answer a rejected request gets. The names are the usual HTTP ones: 404, 400, 403, 409. */
26
+ export type CommandErrorCode = 'NOT_FOUND' | 'BAD_REQUEST' | 'FORBIDDEN' | 'CONFLICT';
27
+ /**
28
+ * A business rule rejected the request. App code throws it: a command handler,
29
+ * or the `events` function of a `write`. The code names the kind of answer and
30
+ * the message is written for the client.
31
+ *
32
+ * It is NOT a `JournalError`. A `JournalError` is the library's own failure: a
33
+ * row or a history it refuses. A `CommandError` is the app's rule saying no.
34
+ * The library never throws one and never changes one: like every error thrown
35
+ * by app code, it leaves `journal.run` as the same instance, after the
36
+ * command's transaction rolled back.
37
+ *
38
+ * Nothing here knows HTTP. The host maps the code to its own transport:
39
+ * `@arki/journal/orpc` does it for oRPC, and any other host matches on the
40
+ * code with `isCommandError`.
41
+ */
42
+ export declare class CommandError extends Error {
43
+ readonly code: CommandErrorCode;
44
+ constructor(code: CommandErrorCode, message: string);
45
+ }
46
+ export declare function isCommandError(error: unknown, code?: CommandErrorCode): error is CommandError;
47
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GACxB,UAAU,GACV,QAAQ,GACR,YAAY,GACZ,eAAe,GACf,kBAAkB,GAClB,YAAY,GACZ,aAAa,GACb,eAAe,GACf,iBAAiB,GACjB,aAAa,GACb,gBAAgB,GAChB,gBAAgB,CAAC;AAsDrB;;;;;;;;;;;;;;GAcG;AACH,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,mFAAmF;IACnF,YAAY,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,EAS1G;CACF;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,KAAK,IAAI,YAAY,CAE7F;AAED,yGAAyG;AACzG,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,aAAa,GAAG,WAAW,GAAG,UAAU,CAAC;AAEtF;;;;;;;;;;;;;;GAcG;AACH,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAEhC,YAAY,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,EAIlD;CACF;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,KAAK,IAAI,YAAY,CAE7F"}