@routier/core 0.2.0 → 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.
Files changed (174) hide show
  1. package/LICENSE +21 -0
  2. package/{readme.md → README.md} +2 -2
  3. package/dist/assertions/index.cjs +191 -0
  4. package/dist/assertions/index.cjs.map +1 -0
  5. package/dist/assertions/index.d.ts +30 -0
  6. package/dist/assertions/index.js +94 -30
  7. package/dist/assertions/index.js.map +1 -1
  8. package/dist/capabilities/index.cjs +820 -0
  9. package/dist/capabilities/index.cjs.map +1 -0
  10. package/dist/capabilities/index.js +580 -545
  11. package/dist/capabilities/index.js.map +1 -1
  12. package/dist/codegen/handlers/CloneHandlerBuilder.d.ts +10 -1
  13. package/dist/codegen/handlers/SetHandlerBuilder.d.ts +4 -0
  14. package/dist/codegen/handlers/clone/CloneArrayHandler.d.ts +3 -0
  15. package/dist/codegen/handlers/clone/CloneDateHandler.d.ts +13 -0
  16. package/dist/codegen/handlers/clone/CloneValueHandler.d.ts +16 -0
  17. package/dist/codegen/handlers/compare/CompareComputedHandler.d.ts +10 -0
  18. package/dist/codegen/handlers/compare/CompareFunctionHandler.d.ts +10 -0
  19. package/dist/codegen/handlers/deserialize/DeserializeArrayHandler.d.ts +14 -0
  20. package/dist/codegen/handlers/enableChangeTracking/EnableChangeTrackingArrayHandler.d.ts +11 -0
  21. package/dist/codegen/handlers/enrichment/EnrichmentArrayHandler.d.ts +11 -0
  22. package/dist/codegen/handlers/freeze/FreezeArrayHandler.d.ts +6 -0
  23. package/dist/codegen/handlers/hash/HashArrayHandler.d.ts +10 -0
  24. package/dist/codegen/handlers/hash/HashFileHandler.d.ts +24 -0
  25. package/dist/codegen/handlers/hash/HashObjectHandler.d.ts +10 -0
  26. package/dist/codegen/handlers/hash/HashTransformHandler.d.ts +23 -0
  27. package/dist/codegen/handlers/index.d.ts +1 -0
  28. package/dist/codegen/handlers/merge/MergeArrayHandler.d.ts +20 -0
  29. package/dist/codegen/handlers/merge/MergeObjectHandler.d.ts +10 -0
  30. package/dist/codegen/handlers/serialize/SerializeArrayHandler.d.ts +19 -0
  31. package/dist/codegen/handlers/serialize/SerializeComputedHandler.d.ts +6 -0
  32. package/dist/codegen/handlers/set/SetComplexHandler.d.ts +6 -0
  33. package/dist/codegen/handlers/strip/StripComputedHandler.d.ts +10 -0
  34. package/dist/codegen/handlers/strip/StripFunctionHandler.d.ts +9 -0
  35. package/dist/codegen/handlers/types.d.ts +55 -1
  36. package/dist/codegen/index.cjs +618 -0
  37. package/dist/codegen/index.cjs.map +1 -0
  38. package/dist/codegen/index.js +100 -127
  39. package/dist/codegen/index.js.map +1 -1
  40. package/dist/collections/Changes.d.ts +2 -2
  41. package/dist/collections/MemoryDataCollection.d.ts +15 -1
  42. package/dist/collections/TagCollection.d.ts +7 -8
  43. package/dist/collections/index.cjs +702 -0
  44. package/dist/collections/index.cjs.map +1 -0
  45. package/dist/collections/index.js +282 -276
  46. package/dist/collections/index.js.map +1 -1
  47. package/dist/errors/OptimisticConcurrencyError.d.ts +18 -0
  48. package/dist/errors/PluginDestroyedError.d.ts +10 -0
  49. package/dist/errors/index.cjs +132 -0
  50. package/dist/errors/index.cjs.map +1 -0
  51. package/dist/errors/index.d.ts +2 -0
  52. package/dist/errors/index.js +61 -19
  53. package/dist/errors/index.js.map +1 -1
  54. package/dist/expressions/constants.d.ts +2 -0
  55. package/dist/expressions/evaluate.d.ts +53 -0
  56. package/dist/expressions/index.cjs +2087 -0
  57. package/dist/expressions/index.cjs.map +1 -0
  58. package/dist/expressions/index.d.ts +2 -0
  59. package/dist/expressions/index.js +1784 -772
  60. package/dist/expressions/index.js.map +1 -1
  61. package/dist/expressions/parser.d.ts +19 -0
  62. package/dist/expressions/types.d.ts +87 -2
  63. package/dist/expressions/utils.d.ts +1 -2
  64. package/dist/index.cjs +13925 -0
  65. package/dist/index.cjs.map +1 -0
  66. package/dist/index.js +12094 -7049
  67. package/dist/index.js.map +1 -1
  68. package/dist/performance/index.cjs +217 -0
  69. package/dist/performance/index.cjs.map +1 -0
  70. package/dist/performance/index.js +126 -50
  71. package/dist/performance/index.js.map +1 -1
  72. package/dist/pipeline/index.cjs +576 -0
  73. package/dist/pipeline/index.cjs.map +1 -0
  74. package/dist/pipeline/index.js +229 -156
  75. package/dist/pipeline/index.js.map +1 -1
  76. package/dist/plugins/BatchingDbPlugin.d.ts +139 -0
  77. package/dist/plugins/CacheDbPlugin.d.ts +70 -0
  78. package/dist/plugins/ConcurrencyDbPlugin.d.ts +78 -0
  79. package/dist/plugins/EphemeralDataPlugin.d.ts +57 -1
  80. package/dist/plugins/RetryDbPlugin.d.ts +77 -0
  81. package/dist/plugins/index.cjs +6104 -0
  82. package/dist/plugins/index.cjs.map +1 -0
  83. package/dist/plugins/index.d.ts +5 -1
  84. package/dist/plugins/index.js +5572 -1491
  85. package/dist/plugins/index.js.map +1 -1
  86. package/dist/plugins/query/QueryOptionsCollection.d.ts +22 -0
  87. package/dist/plugins/query/index.d.ts +2 -0
  88. package/dist/plugins/query/join.d.ts +210 -0
  89. package/dist/plugins/query/similarity.d.ts +38 -0
  90. package/dist/plugins/query/types.d.ts +64 -1
  91. package/dist/plugins/translators/DataTranslator.d.ts +28 -0
  92. package/dist/plugins/translators/JsonTranslator.d.ts +27 -0
  93. package/dist/plugins/translators/SqlTranslator.d.ts +45 -0
  94. package/dist/plugins/translators/TranslatedArrayValue.d.ts +3 -1
  95. package/dist/plugins/translators/TranslatedGroupValue.d.ts +3 -1
  96. package/dist/plugins/translators/TranslatedSingleValue.d.ts +3 -1
  97. package/dist/plugins/translators/TupleTranslator.d.ts +45 -0
  98. package/dist/plugins/translators/index.d.ts +1 -0
  99. package/dist/plugins/translators/types.d.ts +8 -1
  100. package/dist/plugins/types.d.ts +87 -11
  101. package/dist/plugins/wire/handler.d.ts +122 -0
  102. package/dist/plugins/wire/index.d.ts +4 -0
  103. package/dist/plugins/wire/persist.d.ts +43 -0
  104. package/dist/plugins/wire/query.d.ts +49 -0
  105. package/dist/plugins/wire/types.d.ts +123 -0
  106. package/dist/results/Result.d.ts +2 -2
  107. package/dist/results/index.cjs +174 -0
  108. package/dist/results/index.cjs.map +1 -0
  109. package/dist/results/index.js +29 -44
  110. package/dist/results/index.js.map +1 -1
  111. package/dist/schema/PropertyInfo.d.ts +35 -2
  112. package/dist/schema/SchemaDefinition.d.ts +51 -1
  113. package/dist/schema/builder.d.ts +35 -1
  114. package/dist/schema/communication/broadcast.d.ts +17 -1
  115. package/dist/schema/index.cjs +6911 -0
  116. package/dist/schema/index.cjs.map +1 -0
  117. package/dist/schema/index.d.ts +4 -0
  118. package/dist/schema/index.js +5781 -3973
  119. package/dist/schema/index.js.map +1 -1
  120. package/dist/schema/property/base/SchemaBase.d.ts +50 -2
  121. package/dist/schema/property/modifiers/SchemaDeserialize.d.ts +5 -1
  122. package/dist/schema/property/modifiers/SchemaForeignKey.d.ts +15 -0
  123. package/dist/schema/property/modifiers/SchemaKey.d.ts +2 -0
  124. package/dist/schema/property/modifiers/SchemaNullable.d.ts +15 -1
  125. package/dist/schema/property/modifiers/SchemaOptional.d.ts +22 -1
  126. package/dist/schema/property/modifiers/SchemaSearchable.d.ts +34 -0
  127. package/dist/schema/property/modifiers/SchemaTag.d.ts +27 -0
  128. package/dist/schema/property/modifiers/SchemaTracked.d.ts +5 -1
  129. package/dist/schema/property/modifiers/index.d.ts +3 -0
  130. package/dist/schema/property/types/SchemaArray.d.ts +2 -0
  131. package/dist/schema/property/types/SchemaBoolean.d.ts +2 -0
  132. package/dist/schema/property/types/SchemaDate.d.ts +2 -0
  133. package/dist/schema/property/types/SchemaFile.d.ts +60 -0
  134. package/dist/schema/property/types/SchemaNumber.d.ts +5 -1
  135. package/dist/schema/property/types/SchemaObject.d.ts +2 -0
  136. package/dist/schema/property/types/SchemaString.d.ts +25 -1
  137. package/dist/schema/property/types/SchemaVector.d.ts +61 -0
  138. package/dist/schema/property/types/index.d.ts +2 -0
  139. package/dist/schema/table/SchemaTransform.d.ts +40 -0
  140. package/dist/schema/table/index.d.ts +1 -0
  141. package/dist/schema/types.d.ts +200 -32
  142. package/dist/schema/utils/propertyKind.d.ts +4 -0
  143. package/dist/schema/utils/standardJsonSchema.d.ts +90 -0
  144. package/dist/types/index.cjs +23 -0
  145. package/dist/types/index.cjs.map +1 -0
  146. package/dist/types/index.js +0 -14
  147. package/dist/types/index.js.map +1 -1
  148. package/dist/utilities/functions.d.ts +1 -1
  149. package/dist/utilities/index.cjs +982 -0
  150. package/dist/utilities/index.cjs.map +1 -0
  151. package/dist/utilities/index.js +590 -321
  152. package/dist/utilities/index.js.map +1 -1
  153. package/dist/utilities/logger.d.ts +48 -0
  154. package/package.json +152 -98
  155. package/dist/collections/Changes.test.d.ts +0 -1
  156. package/dist/collections/MemoryDataCollection.test.d.ts +0 -1
  157. package/dist/collections/TagCollection.test.d.ts +0 -1
  158. package/dist/expressions/parser.test.d.ts +0 -1
  159. package/dist/expressions/utils.test.d.ts +0 -1
  160. package/dist/pipeline/SyncronousQueue.test.d.ts +0 -1
  161. package/dist/pipeline/WorkPipeline.test.d.ts +0 -1
  162. package/dist/plugins/EphemeralDataPlugin.test.d.ts +0 -1
  163. package/dist/plugins/query/QueryOptionsCollection.test.d.ts +0 -1
  164. package/dist/plugins/replication/OptimisticReplicationDbPlugin.d.ts +0 -23
  165. package/dist/plugins/replication/ReplicationDbPlugin.d.ts +0 -22
  166. package/dist/plugins/replication/index.d.ts +0 -3
  167. package/dist/plugins/replication/types.d.ts +0 -5
  168. package/dist/plugins/translators/JsonTranslator.test.d.ts +0 -1
  169. package/dist/schema/PropertyInfo.test.d.ts +0 -1
  170. package/dist/schema/schemaGeneration.test.d.ts +0 -1
  171. package/dist/schema/testSchemas.test.d.ts +0 -314
  172. package/dist/utilities/arrays.test.d.ts +0 -1
  173. package/dist/utilities/dates.test.d.ts +0 -1
  174. package/dist/utilities/strings.test.d.ts +0 -1
@@ -7,6 +7,28 @@ import { ITranslatedValue } from "./translators";
7
7
  * Interface for a database plugin, which provides query, destroy, and bulk operations.
8
8
  */
9
9
  export interface IDbPlugin {
10
+ /**
11
+ * Uniquely identifies the database this plugin talks to, INCLUDING host or path where a
12
+ * bare name would collide — `orders.db` in two directories is two databases, and `mydb`
13
+ * on two hosts is two databases. Two instances over the same database must return the
14
+ * same string, in this process and in any other; two over different databases must not.
15
+ *
16
+ * Used to scope schema subscription channels, so instances of one database (another tab,
17
+ * a worker) see each other's change notifications and unrelated databases holding the
18
+ * same schema do not.
19
+ *
20
+ * Required rather than optional on purpose. An absent value used to fall back to scoping
21
+ * by schema alone, which shares one channel across every database holding that schema —
22
+ * the exact cross-talk this prevents, arrived at by omission. Requiring it also makes a
23
+ * wrapper that forgets to forward it a compile error rather than a silent regression.
24
+ *
25
+ * Derive it, never generate it: a random value is unique per PROCESS, not per database,
26
+ * so another tab would never match one and cross-context notifications would stop.
27
+ *
28
+ * Must not contain credentials — it becomes part of a channel key, so build it from
29
+ * host/port/database rather than returning a connection string.
30
+ */
31
+ readonly databaseName: string;
10
32
  /**
11
33
  * Executes a query operation on the database.
12
34
  * @param event The query event containing schema, parent, and query operation.
@@ -41,8 +63,12 @@ export type DbPluginEvent = {
41
63
  schemas: SchemaCollection;
42
64
  /** Unique id of the event. */
43
65
  id: string;
44
- /** Source of the request */
45
- source: "data-store" | "collection" | "view" | "capability";
66
+ /** The class/component that triggered this event */
67
+ source: string;
68
+ /** The action/operation type being performed */
69
+ action: "query" | "persist" | "destroy";
70
+ /** Optional context about why this operation is happening */
71
+ reason?: string;
46
72
  };
47
73
  /**
48
74
  * Event for a specific plugin operation, extending the base event with an operation payload.
@@ -67,20 +93,69 @@ export type ReplicationPluginOptions = {
67
93
  */
68
94
  read?: IDbPlugin;
69
95
  };
70
- export type OptimisticReplicationPluginOptions = {
71
- /** The primary database plugin that handles all write operations, do not include in the list of replicas. */
72
- source: IDbPlugin;
73
- /** Array of replica database plugins that can be used for read operations. */
74
- replicas: IDbPlugin[];
75
- /** Must be a MemoryPlugin */
76
- read: IDbPlugin;
96
+ /**
97
+ * A value inside a delta. Arrays and Dates are values, not sub-structures to descend into.
98
+ *
99
+ * Descending into them would be both wrong and useless: an element-wise array delta cannot
100
+ * express "the last element was removed", and a partial Date is meaningless.
101
+ */
102
+ type DeltaValue<V> = V extends readonly unknown[] ? V : V extends Date ? V : V extends object ? DeltaProperties<V> : V;
103
+ type DeltaProperties<T> = {
104
+ [K in keyof T]?: DeltaValue<T[K]>;
77
105
  };
106
+ /**
107
+ * What changed about an entity, expressed as a **partial entity**.
108
+ *
109
+ * A change two levels deep appears where it actually lives —
110
+ * `{ nested: { inner: { value } } }` — not as a flattened key.
111
+ *
112
+ * This deliberately carries no storage vocabulary. It used to be typed
113
+ * `{ [key: string]: string | number | Date }`, which was wrong twice over: it excluded
114
+ * booleans, nulls, arrays and objects that the schema happily allows, and its flat
115
+ * scalar shape was really a SQL `SET column = ?` list — one storage family's concern
116
+ * leaking into the contract every plugin sees.
117
+ *
118
+ * Translating this into storage terms belongs to the plugin. A document store can merge it
119
+ * as-is; a SQL plugin decides which columns it touches and how a nested value is encoded
120
+ * (see `toColumnAssignments` in `@routier/sql-plugin-core`, which stores nested objects and
121
+ * arrays as JSON). Core does not need to know, and must not.
122
+ */
123
+ export type EntityDelta<T extends {}> = DeltaProperties<InferType<T>>;
78
124
  export type EntityUpdateInfo<T extends {}> = {
79
125
  entity: InferType<T>;
80
126
  changeType: EntityChangeType;
81
- delta: {
82
- [key: string]: string | number | Date;
127
+ delta: EntityDelta<T>;
128
+ /**
129
+ * Present when the schema declares a `.concurrency()` token: the update must be
130
+ * applied ONLY IF the stored row's `column` still equals `expected` (the value the
131
+ * writer read). The entity/delta already carry the bumped value to store on success.
132
+ * A plugin that finds a mismatch must fail the whole save with an
133
+ * OptimisticConcurrencyError naming the conflicted rows — never apply partially.
134
+ */
135
+ concurrency?: {
136
+ column: string;
137
+ expected: number;
83
138
  };
139
+ /**
140
+ * The values these properties held BEFORE this update — keyed like `delta`, which holds
141
+ * the values they hold after.
142
+ *
143
+ * DATASTORE-INTERNAL. The datastore strips it before the plugin is called
144
+ * (`DataStore.onSavePreparedChanges`), so no plugin ever receives it and nothing goes over
145
+ * a wire. It exists for save-pipeline participants that must undo work keyed by an old
146
+ * value — a search index has to delete the rows for terms that just left a field, and
147
+ * `delta` only says what the field says now.
148
+ *
149
+ * Always populated for an update. It is part of what an update IS, not something a
150
+ * declaration switches on — a consumer can rely on it without knowing what else the store
151
+ * declared, and there is one code path to reason about rather than two.
152
+ *
153
+ * Which properties appear depends on what the change-tracking mode can know. Proxy and
154
+ * immutable name exactly the properties that changed. Diff detects change by comparing a
155
+ * content hash, so it cannot say WHICH property moved and reports every root property —
156
+ * the same "assume everything" convention its empty `delta` already uses.
157
+ */
158
+ previous?: EntityDelta<T>;
84
159
  };
85
160
  export type TaggedEntity<T> = {
86
161
  entity: T;
@@ -100,3 +175,4 @@ export type IQuery<TRoot extends {}, TShape> = {
100
175
  get changeTracking(): boolean;
101
176
  };
102
177
  export type EntityChangeType = "propertiesChanged" | "markedDirty" | "notModified";
178
+ export {};
@@ -0,0 +1,122 @@
1
+ import { ReadonlySchemaCollection } from "../../collections/ReadonlySchemaCollection";
2
+ import { Filter, ParamsFilter } from "../../expressions";
3
+ import { CompiledSchema } from "../../schema";
4
+ import { IDbPlugin } from "../types";
5
+ import { ScopeProvider } from "./query";
6
+ import { SerializedRequest, SerializedResponse } from "./types";
7
+ /**
8
+ * The receiving half: takes a serialized request, executes it, returns a serialized response.
9
+ *
10
+ * Deliberately not a server. It is one async function from JSON to JSON, with no notion of HTTP, so
11
+ * the same handler sits behind Express, a Cloudflare Worker, a Lambda, a WebSocket message, or a
12
+ * worker `postMessage`. Transport is the caller's business; this is the part that would otherwise be
13
+ * rewritten per framework.
14
+ *
15
+ * ```ts
16
+ * const handle = createRequestHandler({ plugin, schemas });
17
+ *
18
+ * app.post("/routier", async (req, res) => res.json(await handle(req.body)));
19
+ * ```
20
+ *
21
+ * ## Security is YOURS, and this gives you the two places to put it
22
+ *
23
+ * There is no built-in notion of a user, a tenant, a role or a token, and there should not be: this
24
+ * library cannot know what your identities are or what they may see. What it can do is make sure
25
+ * there is nowhere for a decision to be forgotten. Two hooks, and both receive a `context` you built
26
+ * from the request:
27
+ *
28
+ * - **`authorize`** — may this caller do this at all? Called once per request, before anything is
29
+ * deserialized or executed, with the action and every collection the request touches.
30
+ * - **`scope`** — which ROWS may this caller see? A filter the receiver ANDs into every read of a
31
+ * collection, and checks every written row against.
32
+ *
33
+ * With neither supplied, the endpoint answers anything for anyone. That is the correct default for a
34
+ * function with no idea who is calling it — and the moment you name a context type, passing one
35
+ * becomes required, so a policy cannot be half-wired.
36
+ *
37
+ * ```ts
38
+ * const handle = createRequestHandler<{ tenantId: string }>({
39
+ * plugin,
40
+ * schemas,
41
+ * authorize: ({ action, context }) => context.tenantId != null || "not signed in",
42
+ * scope: ({ context }) => ({ filter: ([row, p]) => row.tenantId === p.tenantId, params: context }),
43
+ * });
44
+ *
45
+ * app.post("/routier", async (req, res) => {
46
+ * const context = { tenantId: req.user?.tenantId }; // from the REQUEST, never from the body
47
+ * res.json(await handle(req.body, context));
48
+ * });
49
+ * ```
50
+ *
51
+ * ## Errors are values
52
+ *
53
+ * A failure comes back as `{ ok: false, error }` rather than as a rejected promise, so a transport
54
+ * cannot accidentally turn a query error into a 500 with no body. The caller decides the status.
55
+ */
56
+ /** What a hook is told about the request it is judging. */
57
+ export type RequestInfo<TContext> = {
58
+ action: "query" | "persist" | "destroy";
59
+ /** Every collection this request touches, including the inner side of any join. */
60
+ collectionNames: string[];
61
+ /** Whatever the transport built from the request — a user, a tenant, a token. Never the body. */
62
+ context: TContext;
63
+ /** The raw request, for a policy that needs to look closer. Treat it as caller-controlled input. */
64
+ request: SerializedRequest;
65
+ };
66
+ /**
67
+ * May this request proceed?
68
+ *
69
+ * `true` to allow. `false` or a string to refuse — a string becomes the error message, which is the
70
+ * cheapest way to say WHY without inventing an error type. Throwing also refuses.
71
+ *
72
+ * Called once, before deserialization, so a refused request never reaches a schema or a plugin.
73
+ */
74
+ export type AuthorizeHook<TContext> = (info: RequestInfo<TContext>) => boolean | string | Promise<boolean | string>;
75
+ /** What a scope hook is asked. One collection at a time, since each may be scoped differently. */
76
+ export type ScopeInfo<TContext> = {
77
+ collectionName: string;
78
+ schema: CompiledSchema<any>;
79
+ context: TContext;
80
+ action: "query" | "persist";
81
+ };
82
+ /**
83
+ * The rows of one collection this caller may touch, as a filter.
84
+ *
85
+ * Written exactly like a collection's own `.scope()` — a filter, optionally with params — so the same
86
+ * expression is pushed into the database on reads and checked against each row on writes. Return
87
+ * `null` for a collection this caller may see in full.
88
+ *
89
+ * ```ts
90
+ * scope: ({ collectionName, context }) =>
91
+ * collectionName === "orders"
92
+ * ? { filter: ([row, p]) => row.tenantId === p.tenantId, params: { tenantId: context.tenantId } }
93
+ * : null
94
+ * ```
95
+ */
96
+ export type ScopeHook<TContext> = (info: ScopeInfo<TContext>) => {
97
+ filter: Filter<any> | ParamsFilter<any, any>;
98
+ params?: {};
99
+ } | null;
100
+ export type RequestHandlerOptions<TContext> = {
101
+ /** The plugin that actually holds the data. Anything implementing `IDbPlugin`. */
102
+ plugin: IDbPlugin;
103
+ /** Every collection this endpoint will answer for. A name absent from here is refused. */
104
+ schemas: ReadonlySchemaCollection;
105
+ /** May this caller do this? See `AuthorizeHook`. Absent means yes, to everyone. */
106
+ authorize?: AuthorizeHook<TContext>;
107
+ /** Which rows may this caller touch? See `ScopeHook`. Absent means all of them. */
108
+ scope?: ScopeHook<TContext>;
109
+ /**
110
+ * Whether a `destroy` request may drop the database. **Defaults to false.**
111
+ *
112
+ * `HttpTransportDbPlugin` never sends one, but an endpoint answers whatever arrives — and a
113
+ * hand-written `{"kind":"destroy"}` would otherwise wipe the store for anyone who could reach
114
+ * the route. Destroying a database is not something a remote caller should be able to ask for by
115
+ * default, so it is opt-in and still passes through `authorize`.
116
+ */
117
+ allowDestroy?: boolean;
118
+ };
119
+ export type RequestHandler<TContext> = (request: SerializedRequest, context: TContext) => Promise<SerializedResponse>;
120
+ export declare const createRequestHandler: <TContext = void>(options: RequestHandlerOptions<TContext>) => RequestHandler<TContext>;
121
+ /** Re-exported so a caller can type its own scope provider without reaching for the query module. */
122
+ export type { ScopeProvider };
@@ -0,0 +1,4 @@
1
+ export * from './types';
2
+ export * from './query';
3
+ export * from './persist';
4
+ export * from './handler';
@@ -0,0 +1,43 @@
1
+ import { BulkPersistChanges, BulkPersistResult } from "../../collections";
2
+ import { CompiledSchema } from "../../schema";
3
+ import { ReadonlySchemaCollection } from "../../collections/ReadonlySchemaCollection";
4
+ import { SchemaResolver } from "./query";
5
+ import { SerializedPersistRequest, SerializedResponse } from "./types";
6
+ /**
7
+ * Saves, in the form that survives a wire.
8
+ *
9
+ * Simpler than a query, because a change set holds no functions: adds, updates and removes are
10
+ * entities, and an entity reaching a plugin has already been through `preprocess` — so it is in
11
+ * STORAGE shape, where a Date is already an ISO string and a nested object is already whatever the
12
+ * schema said to store. It is JSON by the time it gets here.
13
+ *
14
+ * Two things are deliberately left behind:
15
+ *
16
+ * - **Tags.** `SchemaPersistChanges.tags` is caller-side metadata for correlating a save with its
17
+ * echo locally. The receiver has no use for it and no business seeing it.
18
+ * - **Schema ids.** Collections are NAMED. An id is a hash of the schema's own shape, so it would
19
+ * couple both sides to identical schema definitions; a name lets the receiver resolve its own.
20
+ */
21
+ export declare const serializeBulkPersist: (changes: BulkPersistChanges, schemas: ReadonlySchemaCollection) => SerializedPersistRequest;
22
+ /**
23
+ * Rebuilds a change set from its wire form, keyed by the RECEIVER's schema ids.
24
+ *
25
+ * @throws when a named collection is not one this store declares. A save aimed at data this side
26
+ * does not have must not be silently dropped — the caller would be told it succeeded.
27
+ */
28
+ export declare const deserializeBulkPersist: (request: SerializedPersistRequest, resolveSchema: SchemaResolver) => {
29
+ changes: BulkPersistChanges;
30
+ schemas: CompiledSchema<any>[];
31
+ };
32
+ /**
33
+ * Serializes the ECHO of a save — the part the change tracker cannot do without.
34
+ *
35
+ * A save's result is not a receipt. It carries the rows as the database wrote them, including any
36
+ * identity the database assigned, and the change tracker matches each one back to the addition that
37
+ * produced it. Returning a count instead would leave every inserted entity without its key.
38
+ */
39
+ export declare const serializePersistResult: (result: BulkPersistResult, schemas: ReadonlySchemaCollection) => SerializedResponse;
40
+ /** Rebuilds a save's echo against the SENDER's schema ids, which is what its change tracker holds. */
41
+ export declare const deserializePersistResult: (response: Extract<SerializedResponse, {
42
+ kind: "persist";
43
+ }>, resolveSchema: SchemaResolver) => BulkPersistResult;
@@ -0,0 +1,49 @@
1
+ import { Expression } from "../../expressions";
2
+ import { CompiledSchema } from "../../schema";
3
+ import { QueryOptionsCollection } from "../query/QueryOptionsCollection";
4
+ import { SerializedQueryOption } from "./types";
5
+ /**
6
+ * Splits options into the PREFIX that can be sent and the remainder that cannot.
7
+ *
8
+ * A prefix, not a filtered subset, and that is the whole correctness argument. Options are ordered,
9
+ * and most are not idempotent: sending `count` while keeping `map` local would count unmapped rows,
10
+ * and applying `take` on both sides would window twice. So the split stops at the first option that
11
+ * cannot travel, and everything from there on runs where the closures are.
12
+ *
13
+ * It is the same shape as the database/memory split this composes with — one more cut of the same
14
+ * ordered list, for one more reason.
15
+ */
16
+ export declare const splitSendableOptions: <T>(options: QueryOptionsCollection<T>) => {
17
+ sendable: QueryOptionsCollection<T>;
18
+ local: QueryOptionsCollection<T>;
19
+ };
20
+ export declare const serializeQueryOptions: <T>(options: QueryOptionsCollection<T>) => SerializedQueryOption[];
21
+ /** How the receiver finds a collection it was sent the NAME of. */
22
+ export type SchemaResolver = (collectionName: string) => CompiledSchema<any> | null;
23
+ /**
24
+ * A filter the RECEIVER adds to every read of a collection, whatever the sender asked for.
25
+ *
26
+ * Returns `null` for a collection with nothing to add. See `createRequestHandler` for the policy
27
+ * side; this is only how it reaches the options.
28
+ */
29
+ export type ScopeProvider = (schema: CompiledSchema<any>) => Expression | null;
30
+ /**
31
+ * Rebuilds query options from their wire form, against the receiver's own schemas.
32
+ *
33
+ * The closures that were dropped are reconstructed here rather than sent:
34
+ *
35
+ * - a **sort selector** from its property, which is all `JsonTranslator.sort` reads;
36
+ * - a **filter predicate** from its expression tree, via `toStrictPredicate` — which THROWS rather
37
+ * than keeping a row it cannot judge, because on a receiver a filter that quietly stops filtering
38
+ * returns rows the requester excluded.
39
+ *
40
+ * @throws when a named property or collection is not declared by the receiver's schemas. A payload
41
+ * describing data this side does not have is a disagreement, and it has to be loud.
42
+ */
43
+ export declare const deserializeQueryOptions: (serialized: SerializedQueryOption[], schema: CompiledSchema<any>, resolveSchema: SchemaResolver,
44
+ /**
45
+ * A receiver-side filter per collection, applied to this collection AND to every collection a
46
+ * join reaches. Prepended, so it is ANDed with whatever the sender sent and there is no order of
47
+ * options that removes it.
48
+ */
49
+ scopeFor?: ScopeProvider) => QueryOptionsCollection<any>;
@@ -0,0 +1,123 @@
1
+ import { SerializedExpression } from "../../expressions";
2
+ import { JoinKind } from "../query/join";
3
+ import { QueryOrdering } from "../query/types";
4
+ /**
5
+ * The wire format for a whole Routier operation.
6
+ *
7
+ * This is what makes a plugin that owns no database possible: the query itself travels, and a
8
+ * receiver executes it against whatever plugin IT has. Nothing here is HTTP-specific — it is plain
9
+ * JSON, so the same payload works over fetch, a WebSocket, a worker `postMessage`, or a queue.
10
+ *
11
+ * ## Two rules shape every type below
12
+ *
13
+ * 1. **No functions cross.** A query option carries live closures — a sort selector, a filter
14
+ * predicate, a map projection — and none of them survive `JSON.stringify`. Where a closure can
15
+ * be REBUILT from data it is dropped and reconstructed on arrival (a sort selector from its
16
+ * property, a filter predicate from its expression tree). Where it cannot, the option does not
17
+ * travel at all and the sender runs it locally; see `map` and `group`.
18
+ * 2. **The receiver's schema is the authority.** Collections are named, never described. A payload
19
+ * says "teams", and the receiver resolves its own compiled schema for that name. Sending a
20
+ * schema would let the sender decide what its properties are, which is backwards for anything
21
+ * crossing a trust boundary — and it is the same reason `expressionFromJson` takes the schema
22
+ * rather than reading an id out of the payload.
23
+ */
24
+ /** A query option, in the form that survives a wire. */
25
+ export type SerializedQueryOption = {
26
+ name: "skip";
27
+ value: number;
28
+ } | {
29
+ name: "take";
30
+ value: number;
31
+ }
32
+ /** The selector is dropped and rebuilt from the property on arrival. */
33
+ | {
34
+ name: "sort";
35
+ value: {
36
+ propertyName: string;
37
+ direction: QueryOrdering;
38
+ };
39
+ }
40
+ /**
41
+ * The expression only — no closure and no params bag.
42
+ *
43
+ * A filter reaching a query option is already BOUND: `ParamReferenceExpression` never escapes
44
+ * the parser, so every param value is already a literal in the tree. The receiver rebuilds a
45
+ * runnable predicate from the tree with `toStrictPredicate`.
46
+ */
47
+ | {
48
+ name: "filter";
49
+ value: {
50
+ expression: SerializedExpression;
51
+ };
52
+ } | {
53
+ name: "nearest";
54
+ value: {
55
+ propertyName: string;
56
+ vector: number[];
57
+ count: number;
58
+ };
59
+ } | {
60
+ name: "join";
61
+ value: {
62
+ kind: JoinKind;
63
+ /** Named, not described — the receiver resolves its own schema for it. */
64
+ innerCollectionName: string;
65
+ outerKeyPath: string;
66
+ innerKeyPath: string;
67
+ innerOptions: SerializedQueryOption[];
68
+ semiJoinKeyThreshold: number;
69
+ };
70
+ } | {
71
+ name: "count" | "min" | "max" | "sum" | "distinct";
72
+ value: true;
73
+ };
74
+ export type SerializedQueryRequest = {
75
+ kind: "query";
76
+ collectionName: string;
77
+ options: SerializedQueryOption[];
78
+ };
79
+ /** One entity update, as `EntityUpdateInfo` minus nothing — every field of it is already JSON. */
80
+ export type SerializedUpdate = {
81
+ entity: unknown;
82
+ changeType: "propertiesChanged" | "markedDirty" | "notModified";
83
+ delta: unknown;
84
+ concurrency?: {
85
+ column: string;
86
+ expected: number;
87
+ };
88
+ };
89
+ export type SerializedSchemaChanges = {
90
+ collectionName: string;
91
+ adds: unknown[];
92
+ updates: SerializedUpdate[];
93
+ removes: unknown[];
94
+ };
95
+ export type SerializedPersistRequest = {
96
+ kind: "persist";
97
+ changes: SerializedSchemaChanges[];
98
+ };
99
+ export type SerializedDestroyRequest = {
100
+ kind: "destroy";
101
+ };
102
+ export type SerializedRequest = SerializedQueryRequest | SerializedPersistRequest | SerializedDestroyRequest;
103
+ /** What a receiver sends back. Errors are a value, not a transport status. */
104
+ export type SerializedResponse = {
105
+ ok: true;
106
+ kind: "query";
107
+ value: unknown;
108
+ } | {
109
+ ok: true;
110
+ kind: "persist";
111
+ changes: Array<{
112
+ collectionName: string;
113
+ adds: unknown[];
114
+ updates: unknown[];
115
+ removes: unknown[];
116
+ }>;
117
+ } | {
118
+ ok: true;
119
+ kind: "destroy";
120
+ } | {
121
+ ok: false;
122
+ error: string;
123
+ };
@@ -11,13 +11,13 @@ declare abstract class BaseResult {
11
11
  }
12
12
  export declare class Result extends BaseResult {
13
13
  static success<T>(data: T): ResultType<T>;
14
- static success<T>(): ResultType<never>;
14
+ static success(): ResultType<never>;
15
15
  static error<T>(error: any): ResultType<T>;
16
16
  static partial<T>(data: T, error: any): PartialResultType<T>;
17
17
  }
18
18
  export declare class PluginEventResult extends BaseResult {
19
19
  static success<T>(id: string, data: T): PluginEventResultType<T>;
20
- static success<T>(id: string): PluginEventResultType<never>;
20
+ static success(id: string): PluginEventResultType<never>;
21
21
  static error<T>(id: string, error: any): PluginEventResultType<T>;
22
22
  static partial<T>(id: string, data: T, error: any): PluginEventPartialResultType<T>;
23
23
  static assertSuccess<T>(result: PluginEventResultType<T>): asserts result is {
@@ -0,0 +1,174 @@
1
+ (() => {
2
+ "use strict";
3
+ var __webpack_modules__ = ({
4
+ 718(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
5
+ __webpack_require__.d(__webpack_exports__, {
6
+ D: () => (PluginEventResult),
7
+ Q: () => (Result)
8
+ });
9
+ class BaseResult {
10
+ static ERROR = "error";
11
+ static SUCCESS = "success";
12
+ static PARTIAL = "partial";
13
+ static resolve(result, resolve, reject) {
14
+ if (result.ok === BaseResult.SUCCESS) {
15
+ resolve(result.data);
16
+ return;
17
+ }
18
+ if (result.ok === BaseResult.PARTIAL) {
19
+ reject({
20
+ partial: result.data,
21
+ error: result.error
22
+ });
23
+ return;
24
+ }
25
+ reject(result.error);
26
+ }
27
+ static assertSuccess(result) {
28
+ if (result.ok !== BaseResult.SUCCESS) {
29
+ throw new Error(`Expected success result, but got ${result.ok}: ${result.error}`);
30
+ }
31
+ }
32
+ }
33
+ class Result extends BaseResult {
34
+ static success(data) {
35
+ return {
36
+ ok: Result.SUCCESS,
37
+ data
38
+ };
39
+ }
40
+ static error(error) {
41
+ return {
42
+ ok: Result.ERROR,
43
+ error
44
+ };
45
+ }
46
+ static partial(data, error) {
47
+ return {
48
+ ok: Result.PARTIAL,
49
+ data,
50
+ error
51
+ };
52
+ }
53
+ }
54
+ class PluginEventResult extends BaseResult {
55
+ static success(id, data) {
56
+ return {
57
+ id,
58
+ ok: Result.SUCCESS,
59
+ data
60
+ };
61
+ }
62
+ static error(id, error) {
63
+ return {
64
+ id,
65
+ ok: Result.ERROR,
66
+ error
67
+ };
68
+ }
69
+ static partial(id, data, error) {
70
+ return {
71
+ id,
72
+ ok: Result.PARTIAL,
73
+ data,
74
+ error
75
+ };
76
+ }
77
+ static assertSuccess(result) {
78
+ if (result.ok !== Result.SUCCESS) {
79
+ throw new Error(`Expected success result, but got ${result.ok}: ${result.error}`);
80
+ }
81
+ }
82
+ }
83
+
84
+
85
+ },
86
+
87
+ });
88
+ // The module cache
89
+ var __webpack_module_cache__ = {};
90
+
91
+ // The require function
92
+ function __webpack_require__(moduleId) {
93
+
94
+ // Check if module is in cache
95
+ var cachedModule = __webpack_module_cache__[moduleId];
96
+ if (cachedModule !== undefined) {
97
+ return cachedModule.exports;
98
+ }
99
+ // Create a new module (and put it into the cache)
100
+ var module = (__webpack_module_cache__[moduleId] = {
101
+ exports: {}
102
+ });
103
+ // Execute the module function
104
+ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
105
+
106
+ // Return the exports of the module
107
+ return module.exports;
108
+
109
+ }
110
+
111
+ // webpack/runtime/define_property_getters
112
+ (() => {
113
+ __webpack_require__.d = (exports, definition) => {
114
+ for(var key in definition) {
115
+ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
116
+ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
117
+ }
118
+ }
119
+ };
120
+ })();
121
+ // webpack/runtime/has_own_property
122
+ (() => {
123
+ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
124
+ })();
125
+ // webpack/runtime/make_namespace_object
126
+ (() => {
127
+ // define __esModule on exports
128
+ __webpack_require__.r = (exports) => {
129
+ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
130
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
131
+ }
132
+ Object.defineProperty(exports, '__esModule', { value: true });
133
+ };
134
+ })();
135
+ var __webpack_exports__ = {};
136
+ // This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk.
137
+ (() => {
138
+ // ESM COMPAT FLAG
139
+ __webpack_require__.r(__webpack_exports__);
140
+
141
+ // EXPORTS
142
+ __webpack_require__.d(__webpack_exports__, {
143
+ PluginEventResult: () => (/* reexport */ Result/* .PluginEventResult */.D),
144
+ toPromise: () => (/* reexport */ toPromise),
145
+ Result: () => (/* reexport */ Result/* .Result */.Q)
146
+ });
147
+
148
+ // EXTERNAL MODULE: ./src/results/Result.ts
149
+ var Result = __webpack_require__(718);
150
+ ;// CONCATENATED MODULE: ./src/results/utils.ts
151
+
152
+ function toPromise(fn) {
153
+ return new Promise((resolve, reject)=>{
154
+ fn((r)=>{
155
+ if (r.ok === Result/* .Result.ERROR */.Q.ERROR) {
156
+ reject(r.error);
157
+ return;
158
+ }
159
+ resolve(r.data);
160
+ });
161
+ });
162
+ }
163
+
164
+ ;// CONCATENATED MODULE: ./src/results/index.ts
165
+
166
+
167
+
168
+
169
+ })();
170
+
171
+ module.exports = __webpack_exports__;
172
+ })()
173
+ ;
174
+ //# sourceMappingURL=index.cjs.map