@ontrails/store 1.0.0-beta.14 → 1.0.0-beta.15

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 (92) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +7 -0
  3. package/README.md +126 -61
  4. package/dist/index.d.ts +2 -2
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/internal/signal-identity.d.ts +17 -0
  9. package/dist/internal/signal-identity.d.ts.map +1 -0
  10. package/dist/internal/signal-identity.js +81 -0
  11. package/dist/internal/signal-identity.js.map +1 -0
  12. package/dist/jsonfile/index.d.ts +3 -0
  13. package/dist/jsonfile/index.d.ts.map +1 -0
  14. package/dist/jsonfile/index.js +2 -0
  15. package/dist/jsonfile/index.js.map +1 -0
  16. package/dist/jsonfile/runtime.d.ts +19 -0
  17. package/dist/jsonfile/runtime.d.ts.map +1 -0
  18. package/dist/jsonfile/runtime.js +403 -0
  19. package/dist/jsonfile/runtime.js.map +1 -0
  20. package/dist/jsonfile/types.d.ts +40 -0
  21. package/dist/jsonfile/types.d.ts.map +1 -0
  22. package/dist/{drizzle → jsonfile}/types.js.map +1 -1
  23. package/dist/store.d.ts +8 -18
  24. package/dist/store.d.ts.map +1 -1
  25. package/dist/store.js +152 -94
  26. package/dist/store.js.map +1 -1
  27. package/dist/testing.d.ts +28 -0
  28. package/dist/testing.d.ts.map +1 -0
  29. package/dist/testing.js +90 -0
  30. package/dist/testing.js.map +1 -0
  31. package/dist/trails/crud.d.ts +38 -0
  32. package/dist/trails/crud.d.ts.map +1 -0
  33. package/dist/trails/crud.js +112 -0
  34. package/dist/trails/crud.js.map +1 -0
  35. package/dist/trails/index.d.ts +7 -0
  36. package/dist/trails/index.d.ts.map +1 -0
  37. package/dist/trails/index.js +4 -0
  38. package/dist/trails/index.js.map +1 -0
  39. package/dist/trails/reconcile.d.ts +36 -0
  40. package/dist/trails/reconcile.d.ts.map +1 -0
  41. package/dist/trails/reconcile.js +142 -0
  42. package/dist/trails/reconcile.js.map +1 -0
  43. package/dist/trails/sync.d.ts +25 -0
  44. package/dist/trails/sync.d.ts.map +1 -0
  45. package/dist/trails/sync.js +96 -0
  46. package/dist/trails/sync.js.map +1 -0
  47. package/dist/trails/utils.d.ts +51 -0
  48. package/dist/trails/utils.d.ts.map +1 -0
  49. package/dist/trails/utils.js +65 -0
  50. package/dist/trails/utils.js.map +1 -0
  51. package/dist/types.d.ts +205 -35
  52. package/dist/types.d.ts.map +1 -1
  53. package/package.json +6 -9
  54. package/src/__tests__/crud.test.ts +346 -0
  55. package/src/__tests__/jsonfile.test.ts +502 -0
  56. package/src/__tests__/store.test.ts +239 -33
  57. package/src/__tests__/sync-reconcile.test.ts +646 -0
  58. package/src/index.ts +12 -8
  59. package/src/internal/signal-identity.ts +162 -0
  60. package/src/jsonfile/index.ts +6 -0
  61. package/src/jsonfile/runtime.ts +702 -0
  62. package/src/jsonfile/types.ts +50 -0
  63. package/src/store.ts +296 -134
  64. package/src/testing.ts +175 -0
  65. package/src/trails/crud.ts +355 -0
  66. package/src/trails/index.ts +10 -0
  67. package/src/trails/reconcile.ts +281 -0
  68. package/src/trails/sync.ts +251 -0
  69. package/src/trails/utils.ts +96 -0
  70. package/src/types.ts +355 -63
  71. package/tsconfig.tests.json +10 -0
  72. package/tsconfig.tsbuildinfo +1 -1
  73. package/dist/drizzle/index.d.ts +0 -3
  74. package/dist/drizzle/index.d.ts.map +0 -1
  75. package/dist/drizzle/index.js +0 -2
  76. package/dist/drizzle/index.js.map +0 -1
  77. package/dist/drizzle/runtime.d.ts +0 -21
  78. package/dist/drizzle/runtime.d.ts.map +0 -1
  79. package/dist/drizzle/runtime.js +0 -458
  80. package/dist/drizzle/runtime.js.map +0 -1
  81. package/dist/drizzle/schema.d.ts +0 -15
  82. package/dist/drizzle/schema.d.ts.map +0 -1
  83. package/dist/drizzle/schema.js +0 -322
  84. package/dist/drizzle/schema.js.map +0 -1
  85. package/dist/drizzle/types.d.ts +0 -40
  86. package/dist/drizzle/types.d.ts.map +0 -1
  87. package/src/drizzle/__tests__/drizzle.test.ts +0 -469
  88. package/src/drizzle/index.ts +0 -17
  89. package/src/drizzle/runtime.ts +0 -853
  90. package/src/drizzle/schema.ts +0 -577
  91. package/src/drizzle/types.ts +0 -70
  92. /package/dist/{drizzle → jsonfile}/types.js +0 -0
@@ -0,0 +1,162 @@
1
+ import { signal, ValidationError } from '@ontrails/core';
2
+ import type { Signal } from '@ontrails/core';
3
+ import {
4
+ attachLateBoundSignalRef,
5
+ cloneSignalWithId,
6
+ } from '@ontrails/core/internal/signal-ref';
7
+ import type { z } from 'zod';
8
+
9
+ import type {
10
+ AnyStoreDefinition,
11
+ AnyStoreTable,
12
+ StoreTableSignals,
13
+ } from '../types.js';
14
+
15
+ type MutableTables<TStore extends AnyStoreDefinition> = {
16
+ -readonly [TName in keyof TStore['tables']]: TStore['tables'][TName];
17
+ };
18
+
19
+ type StoreSignalEvent = 'created' | 'removed' | 'updated';
20
+
21
+ const createStoreSignalDescription = (
22
+ tableName: string,
23
+ event: StoreSignalEvent
24
+ ): string => {
25
+ switch (event) {
26
+ case 'created': {
27
+ return `Fired after a "${tableName}" entity is created.`;
28
+ }
29
+ case 'removed': {
30
+ return `Fired after a "${tableName}" entity is removed.`;
31
+ }
32
+ case 'updated': {
33
+ return `Fired after a "${tableName}" entity is updated.`;
34
+ }
35
+ default: {
36
+ throw new Error(`Unsupported store signal event: ${event as string}`);
37
+ }
38
+ }
39
+ };
40
+
41
+ const createStoreSignal = <TPayload>(
42
+ tableName: string,
43
+ event: StoreSignalEvent,
44
+ payload: z.ZodType<TPayload>
45
+ ): Signal<TPayload> =>
46
+ attachLateBoundSignalRef(
47
+ signal(`${tableName}.${event}`, {
48
+ description: createStoreSignalDescription(tableName, event),
49
+ payload,
50
+ }),
51
+ {
52
+ kind: 'store-derived',
53
+ token: Bun.randomUUIDv7(),
54
+ }
55
+ );
56
+
57
+ export const createStoreTableSignals = <TPayload>(
58
+ tableName: string,
59
+ payload: z.ZodType<TPayload>
60
+ ): StoreTableSignals<TPayload> =>
61
+ Object.freeze({
62
+ created: createStoreSignal(tableName, 'created', payload),
63
+ removed: createStoreSignal(tableName, 'removed', payload),
64
+ updated: createStoreSignal(tableName, 'updated', payload),
65
+ });
66
+
67
+ export const composeStoreSignalId = (
68
+ scope: string,
69
+ tableName: string,
70
+ event: StoreSignalEvent
71
+ ): string => `${scope}:${tableName}.${event}`;
72
+
73
+ const bindTableSignals = (
74
+ scope: string,
75
+ table: AnyStoreTable
76
+ ): StoreTableSignals<unknown> =>
77
+ Object.freeze({
78
+ created: cloneSignalWithId(
79
+ table.signals.created,
80
+ composeStoreSignalId(scope, table.name, 'created')
81
+ ),
82
+ removed: cloneSignalWithId(
83
+ table.signals.removed,
84
+ composeStoreSignalId(scope, table.name, 'removed')
85
+ ),
86
+ updated: cloneSignalWithId(
87
+ table.signals.updated,
88
+ composeStoreSignalId(scope, table.name, 'updated')
89
+ ),
90
+ });
91
+
92
+ const collectStoreSignals = <TStore extends AnyStoreDefinition>(
93
+ normalized: MutableTables<TStore>,
94
+ tableNames: readonly Extract<keyof TStore['tables'], string>[]
95
+ ) =>
96
+ Object.freeze(
97
+ tableNames.flatMap((name) => {
98
+ const table = normalized[name];
99
+ return table === undefined
100
+ ? []
101
+ : [table.signals.created, table.signals.updated, table.signals.removed];
102
+ })
103
+ );
104
+
105
+ /**
106
+ * Verifies that a resource id is safe to compose into a scoped signal id.
107
+ *
108
+ * Scoped signal ids are matched by the `SCOPED_SIGNAL_ID` pattern in
109
+ * `@ontrails/core` (`^[^:\s]+:[^:.\s]+(?:\.[^:.\s]+)+$`). A resource id used
110
+ * as the scope segment must therefore be a non-empty string that contains
111
+ * neither `":"` nor any whitespace.
112
+ */
113
+ export const isValidResourceId = (resourceId: string): boolean =>
114
+ resourceId.length > 0 && !resourceId.includes(':') && !/\s/.test(resourceId);
115
+
116
+ const assertValidScope = (scope: string): void => {
117
+ if (!isValidResourceId(scope)) {
118
+ throw new ValidationError(
119
+ `Store resource id "${scope}" is invalid: must be a non-empty string with no ":" characters and no whitespace.`
120
+ );
121
+ }
122
+ };
123
+
124
+ export const bindStoreDefinition = <TStore extends AnyStoreDefinition>(
125
+ definition: TStore,
126
+ scope: string
127
+ ): TStore => {
128
+ assertValidScope(scope);
129
+
130
+ const tableNames = definition.tableNames as readonly Extract<
131
+ keyof TStore['tables'],
132
+ string
133
+ >[];
134
+ const tables = {} as MutableTables<TStore>;
135
+
136
+ for (const tableName of tableNames) {
137
+ const table = definition.tables[tableName];
138
+ if (table === undefined) {
139
+ continue;
140
+ }
141
+
142
+ tables[tableName] = Object.freeze({
143
+ ...table,
144
+ signals: bindTableSignals(scope, table),
145
+ }) as MutableTables<TStore>[typeof tableName];
146
+ }
147
+
148
+ const get =
149
+ 'get' in definition && typeof definition.get === 'function'
150
+ ? <TName extends Extract<keyof TStore['tables'], string>>(name: TName) =>
151
+ tables[name]
152
+ : undefined;
153
+
154
+ return Object.freeze({
155
+ ...definition,
156
+ ...(get ? { get } : {}),
157
+ signals: collectStoreSignals(tables, tableNames),
158
+ tables: Object.freeze(tables),
159
+ }) as TStore;
160
+ };
161
+
162
+ export type { StoreSignalEvent };
@@ -0,0 +1,6 @@
1
+ export { connectJsonFile, jsonFile, jsonFile as store } from './runtime.js';
2
+ export type {
3
+ JsonFileConnection,
4
+ JsonFileStoreOptions,
5
+ JsonFileStoreResource,
6
+ } from './types.js';