@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.
- package/.turbo/turbo-lint.log +1 -1
- package/CHANGELOG.md +7 -0
- package/README.md +126 -61
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/signal-identity.d.ts +17 -0
- package/dist/internal/signal-identity.d.ts.map +1 -0
- package/dist/internal/signal-identity.js +81 -0
- package/dist/internal/signal-identity.js.map +1 -0
- package/dist/jsonfile/index.d.ts +3 -0
- package/dist/jsonfile/index.d.ts.map +1 -0
- package/dist/jsonfile/index.js +2 -0
- package/dist/jsonfile/index.js.map +1 -0
- package/dist/jsonfile/runtime.d.ts +19 -0
- package/dist/jsonfile/runtime.d.ts.map +1 -0
- package/dist/jsonfile/runtime.js +403 -0
- package/dist/jsonfile/runtime.js.map +1 -0
- package/dist/jsonfile/types.d.ts +40 -0
- package/dist/jsonfile/types.d.ts.map +1 -0
- package/dist/{drizzle → jsonfile}/types.js.map +1 -1
- package/dist/store.d.ts +8 -18
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +152 -94
- package/dist/store.js.map +1 -1
- package/dist/testing.d.ts +28 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +90 -0
- package/dist/testing.js.map +1 -0
- package/dist/trails/crud.d.ts +38 -0
- package/dist/trails/crud.d.ts.map +1 -0
- package/dist/trails/crud.js +112 -0
- package/dist/trails/crud.js.map +1 -0
- package/dist/trails/index.d.ts +7 -0
- package/dist/trails/index.d.ts.map +1 -0
- package/dist/trails/index.js +4 -0
- package/dist/trails/index.js.map +1 -0
- package/dist/trails/reconcile.d.ts +36 -0
- package/dist/trails/reconcile.d.ts.map +1 -0
- package/dist/trails/reconcile.js +142 -0
- package/dist/trails/reconcile.js.map +1 -0
- package/dist/trails/sync.d.ts +25 -0
- package/dist/trails/sync.d.ts.map +1 -0
- package/dist/trails/sync.js +96 -0
- package/dist/trails/sync.js.map +1 -0
- package/dist/trails/utils.d.ts +51 -0
- package/dist/trails/utils.d.ts.map +1 -0
- package/dist/trails/utils.js +65 -0
- package/dist/trails/utils.js.map +1 -0
- package/dist/types.d.ts +205 -35
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -9
- package/src/__tests__/crud.test.ts +346 -0
- package/src/__tests__/jsonfile.test.ts +502 -0
- package/src/__tests__/store.test.ts +239 -33
- package/src/__tests__/sync-reconcile.test.ts +646 -0
- package/src/index.ts +12 -8
- package/src/internal/signal-identity.ts +162 -0
- package/src/jsonfile/index.ts +6 -0
- package/src/jsonfile/runtime.ts +702 -0
- package/src/jsonfile/types.ts +50 -0
- package/src/store.ts +296 -134
- package/src/testing.ts +175 -0
- package/src/trails/crud.ts +355 -0
- package/src/trails/index.ts +10 -0
- package/src/trails/reconcile.ts +281 -0
- package/src/trails/sync.ts +251 -0
- package/src/trails/utils.ts +96 -0
- package/src/types.ts +355 -63
- package/tsconfig.tests.json +10 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/drizzle/index.d.ts +0 -3
- package/dist/drizzle/index.d.ts.map +0 -1
- package/dist/drizzle/index.js +0 -2
- package/dist/drizzle/index.js.map +0 -1
- package/dist/drizzle/runtime.d.ts +0 -21
- package/dist/drizzle/runtime.d.ts.map +0 -1
- package/dist/drizzle/runtime.js +0 -458
- package/dist/drizzle/runtime.js.map +0 -1
- package/dist/drizzle/schema.d.ts +0 -15
- package/dist/drizzle/schema.d.ts.map +0 -1
- package/dist/drizzle/schema.js +0 -322
- package/dist/drizzle/schema.js.map +0 -1
- package/dist/drizzle/types.d.ts +0 -40
- package/dist/drizzle/types.d.ts.map +0 -1
- package/src/drizzle/__tests__/drizzle.test.ts +0 -469
- package/src/drizzle/index.ts +0 -17
- package/src/drizzle/runtime.ts +0 -853
- package/src/drizzle/schema.ts +0 -577
- package/src/drizzle/types.ts +0 -70
- /package/dist/{drizzle → jsonfile}/types.js +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crud.js","sourceRoot":"","sources":["../../src/trails/crud.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { crud } from './crud.js';
|
|
2
|
+
export type { CrudBlazeOverrides, CrudOptions, CrudTrails } from './crud.js';
|
|
3
|
+
export { reconcile } from './reconcile.js';
|
|
4
|
+
export type { ReconcileConflict, ReconcileOptions, ReconcileStrategy, } from './reconcile.js';
|
|
5
|
+
export { sync } from './sync.js';
|
|
6
|
+
export type { SyncEndpoint, SyncOptions, SyncTransform } from './sync.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/trails/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,YAAY,EAAE,kBAAkB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EACV,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/trails/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAM3C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { AnySignal, Resource, Trail, TrailContext } from '@ontrails/core';
|
|
2
|
+
import type { AnyStoreTable, EntityOf, StoreAccessor, UpsertOf } from '../types.js';
|
|
3
|
+
type ReconcileConnection<TTable extends AnyStoreTable> = Readonly<Record<TTable['name'], StoreAccessor<TTable>>>;
|
|
4
|
+
export interface ReconcileConflict<TTable extends AnyStoreTable> {
|
|
5
|
+
readonly current: EntityOf<TTable>;
|
|
6
|
+
readonly incoming: UpsertOf<TTable>;
|
|
7
|
+
}
|
|
8
|
+
export type ReconcileStrategy<TTable extends AnyStoreTable> = 'last-write-wins' | ((conflict: ReconcileConflict<TTable>, ctx: TrailContext) => Promise<UpsertOf<TTable>> | UpsertOf<TTable>);
|
|
9
|
+
export interface ReconcileOptions<TTable extends AnyStoreTable, TConnection extends ReconcileConnection<TTable>> {
|
|
10
|
+
readonly description?: string;
|
|
11
|
+
readonly id?: string;
|
|
12
|
+
readonly on?: readonly (AnySignal | string)[];
|
|
13
|
+
readonly resource: Resource<TConnection>;
|
|
14
|
+
readonly strategy?: ReconcileStrategy<TTable>;
|
|
15
|
+
readonly table: TTable;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Produce one trail that retries a versioned upsert with a conflict strategy
|
|
19
|
+
* when the incoming entity is stale.
|
|
20
|
+
*
|
|
21
|
+
* Reconcile is bounded to a single retry via a declarative `detour`. If a
|
|
22
|
+
* concurrent writer races the retry and produces a second `ConflictError`,
|
|
23
|
+
* the detour loop wraps it in `RetryExhaustedError<ConflictError>` so
|
|
24
|
+
* callers can distinguish "retry reconcile at a higher level" from
|
|
25
|
+
* "reconcile tried and lost the race".
|
|
26
|
+
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* For versioned tables, the derived input schema requires an explicit
|
|
29
|
+
* `version` field so callers cannot sidestep optimistic concurrency at the
|
|
30
|
+
* input boundary. `fixtureSchema` alone makes `version` optional because
|
|
31
|
+
* connectors populate it for writes; reconcile must reject that relaxed
|
|
32
|
+
* shape.
|
|
33
|
+
*/
|
|
34
|
+
export declare const reconcile: <TTable extends AnyStoreTable, TConnection extends ReconcileConnection<TTable>>(options: ReconcileOptions<TTable, TConnection>) => Trail<UpsertOf<TTable>, EntityOf<TTable>>;
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=reconcile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../src/trails/reconcile.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,SAAS,EAET,QAAQ,EACR,KAAK,EACL,YAAY,EAGb,MAAM,gBAAgB,CAAC;AAGxB,OAAO,KAAK,EACV,aAAa,EACb,QAAQ,EACR,aAAa,EACb,QAAQ,EACT,MAAM,aAAa,CAAC;AAIrB,KAAK,mBAAmB,CAAC,MAAM,SAAS,aAAa,IAAI,QAAQ,CAC/D,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC,CAC9C,CAAC;AAEF,MAAM,WAAW,iBAAiB,CAAC,MAAM,SAAS,aAAa;IAC7D,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;CACrC;AAED,MAAM,MAAM,iBAAiB,CAAC,MAAM,SAAS,aAAa,IACtD,iBAAiB,GACjB,CAAC,CACC,QAAQ,EAAE,iBAAiB,CAAC,MAAM,CAAC,EACnC,GAAG,EAAE,YAAY,KACd,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;AAEvD,MAAM,WAAW,gBAAgB,CAC/B,MAAM,SAAS,aAAa,EAC5B,WAAW,SAAS,mBAAmB,CAAC,MAAM,CAAC;IAE/C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,SAAS,GAAG,MAAM,CAAC,EAAE,CAAC;IAC9C,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;IACzC,QAAQ,CAAC,QAAQ,CAAC,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC9C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAyLD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,SAAS,GACpB,MAAM,SAAS,aAAa,EAC5B,WAAW,SAAS,mBAAmB,CAAC,MAAM,CAAC,EAE/C,SAAS,gBAAgB,CAAC,MAAM,EAAE,WAAW,CAAC,KAC7C,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CA0B1C,CAAC"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { ConflictError, Result, ValidationError, trail } from '@ontrails/core';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { versionFieldName } from '../store.js';
|
|
4
|
+
import { createTableContour, mapStoreTrailError } from './utils.js';
|
|
5
|
+
const resolveAccessor = (table, resource, ctx) => {
|
|
6
|
+
const connection = resource.from(ctx);
|
|
7
|
+
return connection[table.name];
|
|
8
|
+
};
|
|
9
|
+
const omitUndefined = (value) => Object.fromEntries(Object.entries(value).filter(([, candidate]) => candidate !== undefined));
|
|
10
|
+
const currentVersion = (current) => current[versionFieldName];
|
|
11
|
+
const lastWriteWins = (conflict) => ({
|
|
12
|
+
...conflict.current,
|
|
13
|
+
...omitUndefined(conflict.incoming),
|
|
14
|
+
[versionFieldName]: currentVersion(conflict.current),
|
|
15
|
+
});
|
|
16
|
+
const normalizeResolvedInput = (table, current, resolved) => ({
|
|
17
|
+
...current,
|
|
18
|
+
...omitUndefined(resolved),
|
|
19
|
+
[table.identity]: current[table.identity],
|
|
20
|
+
[versionFieldName]: currentVersion(current),
|
|
21
|
+
});
|
|
22
|
+
const deriveExamples = (table) => {
|
|
23
|
+
const examples = table.fixtures.flatMap((fixture) => {
|
|
24
|
+
const parsed = table.schema.safeParse(fixture);
|
|
25
|
+
if (!parsed.success) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
expected: parsed.data,
|
|
31
|
+
input: fixture,
|
|
32
|
+
name: `Reconcile ${table.name} ${String(fixture[table.identity])}`,
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
});
|
|
36
|
+
return examples.length === 0 ? undefined : Object.freeze(examples);
|
|
37
|
+
};
|
|
38
|
+
const buildConflict = async (table, input, accessor, error) => {
|
|
39
|
+
const identifier = input[table.identity];
|
|
40
|
+
if (identifier === undefined) {
|
|
41
|
+
return error;
|
|
42
|
+
}
|
|
43
|
+
const current = await accessor.get(identifier);
|
|
44
|
+
return current === null ? error : { current, incoming: input };
|
|
45
|
+
};
|
|
46
|
+
const resolveStrategy = async (strategy, conflict, ctx) => strategy === 'last-write-wins'
|
|
47
|
+
? lastWriteWins(conflict)
|
|
48
|
+
: await strategy(conflict, ctx);
|
|
49
|
+
/** Resolve a version conflict through the configured strategy and retry the upsert. */
|
|
50
|
+
const recoverConflict = async (table, input, accessor, error, strategy, ctx) => {
|
|
51
|
+
const conflict = await buildConflict(table, input, accessor, error);
|
|
52
|
+
if (conflict instanceof ConflictError) {
|
|
53
|
+
return Result.err(conflict);
|
|
54
|
+
}
|
|
55
|
+
const resolved = await resolveStrategy(strategy, conflict, ctx);
|
|
56
|
+
const normalized = normalizeResolvedInput(table, conflict.current, resolved);
|
|
57
|
+
return Result.ok(await accessor.upsert(normalized));
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Build the input schema for a reconcile trail.
|
|
61
|
+
*
|
|
62
|
+
* `fixtureSchema` makes generated fields (including `version` on versioned
|
|
63
|
+
* tables) optional because connectors populate them. Reconcile, however,
|
|
64
|
+
* relies on optimistic concurrency: the caller must pass the expected
|
|
65
|
+
* `version` so `assertExpectedVersionMatch` can detect stale payloads. We
|
|
66
|
+
* therefore extend `fixtureSchema` with a required `version` field so
|
|
67
|
+
* callers cannot sidestep optimistic concurrency at the input boundary.
|
|
68
|
+
*/
|
|
69
|
+
const buildReconcileInputSchema = (table) => table.fixtureSchema.extend({
|
|
70
|
+
[versionFieldName]: z.number().int(),
|
|
71
|
+
});
|
|
72
|
+
/** The blaze performs only the initial upsert; conflict recovery is handled by the detour. */
|
|
73
|
+
const createReconcileBlaze = (options, id) => async (input, ctx) => {
|
|
74
|
+
try {
|
|
75
|
+
const accessor = resolveAccessor(options.table, options.resource, ctx);
|
|
76
|
+
return Result.ok(await accessor.upsert(input));
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (error instanceof ConflictError) {
|
|
80
|
+
return Result.err(error);
|
|
81
|
+
}
|
|
82
|
+
return Result.err(mapStoreTrailError(id, error));
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
/** Build the detour that handles ConflictError recovery via the configured strategy. */
|
|
86
|
+
const createReconcileDetour = (options, id, strategy) => ({
|
|
87
|
+
maxAttempts: 1,
|
|
88
|
+
on: ConflictError,
|
|
89
|
+
recover: async (attempt, ctx) => {
|
|
90
|
+
const conflictError = attempt.error;
|
|
91
|
+
try {
|
|
92
|
+
const accessor = resolveAccessor(options.table, options.resource, ctx);
|
|
93
|
+
return await recoverConflict(options.table, attempt.input, accessor, conflictError, strategy, ctx);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (error instanceof ConflictError) {
|
|
97
|
+
return Result.err(error);
|
|
98
|
+
}
|
|
99
|
+
return Result.err(mapStoreTrailError(id, error));
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
/**
|
|
104
|
+
* Produce one trail that retries a versioned upsert with a conflict strategy
|
|
105
|
+
* when the incoming entity is stale.
|
|
106
|
+
*
|
|
107
|
+
* Reconcile is bounded to a single retry via a declarative `detour`. If a
|
|
108
|
+
* concurrent writer races the retry and produces a second `ConflictError`,
|
|
109
|
+
* the detour loop wraps it in `RetryExhaustedError<ConflictError>` so
|
|
110
|
+
* callers can distinguish "retry reconcile at a higher level" from
|
|
111
|
+
* "reconcile tried and lost the race".
|
|
112
|
+
*
|
|
113
|
+
* @remarks
|
|
114
|
+
* For versioned tables, the derived input schema requires an explicit
|
|
115
|
+
* `version` field so callers cannot sidestep optimistic concurrency at the
|
|
116
|
+
* input boundary. `fixtureSchema` alone makes `version` optional because
|
|
117
|
+
* connectors populate it for writes; reconcile must reject that relaxed
|
|
118
|
+
* shape.
|
|
119
|
+
*/
|
|
120
|
+
export const reconcile = (options) => {
|
|
121
|
+
if (!options.table.versioned) {
|
|
122
|
+
throw new ValidationError(`reconcile("${options.table.name}") requires a versioned store table.`);
|
|
123
|
+
}
|
|
124
|
+
const id = options.id ?? `${options.table.name}.reconcile`;
|
|
125
|
+
const entityContour = createTableContour(options.table);
|
|
126
|
+
const strategy = options.strategy ?? 'last-write-wins';
|
|
127
|
+
return trail(id, {
|
|
128
|
+
blaze: createReconcileBlaze(options, id),
|
|
129
|
+
contours: [entityContour],
|
|
130
|
+
description: options.description ??
|
|
131
|
+
`Reconcile version conflicts for "${options.table.name}" entities.`,
|
|
132
|
+
detours: [createReconcileDetour(options, id, strategy)],
|
|
133
|
+
examples: deriveExamples(options.table),
|
|
134
|
+
input: buildReconcileInputSchema(options.table),
|
|
135
|
+
intent: 'write',
|
|
136
|
+
on: options.on,
|
|
137
|
+
output: options.table.schema,
|
|
138
|
+
pattern: 'reconcile',
|
|
139
|
+
resources: [options.resource],
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
//# sourceMappingURL=reconcile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../../src/trails/reconcile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AnySignal, Resource, Trail, TrailContext } from '@ontrails/core';
|
|
2
|
+
import type { AnyStoreTable, EntityOf, ReadOnlyStoreTableAccessor, StoreAccessor, StoreIdentifierOf, UpsertOf } from '../types.js';
|
|
3
|
+
type IdentityInputOf<TTable extends AnyStoreTable> = Readonly<Record<Extract<TTable['identity'], string>, StoreIdentifierOf<TTable>>>;
|
|
4
|
+
type SourceConnection<TTable extends AnyStoreTable> = Readonly<Record<TTable['name'], ReadOnlyStoreTableAccessor<TTable>>>;
|
|
5
|
+
type TargetConnection<TTable extends AnyStoreTable> = Readonly<Record<TTable['name'], StoreAccessor<TTable>>>;
|
|
6
|
+
export interface SyncEndpoint<TTable extends AnyStoreTable, TConnection extends SourceConnection<TTable> | TargetConnection<TTable>> {
|
|
7
|
+
readonly resource: Resource<TConnection>;
|
|
8
|
+
readonly table: TTable;
|
|
9
|
+
}
|
|
10
|
+
export type SyncTransform<TSourceTable extends AnyStoreTable, TTargetTable extends AnyStoreTable> = (entity: EntityOf<TSourceTable>, ctx: TrailContext) => Promise<UpsertOf<TTargetTable>> | UpsertOf<TTargetTable>;
|
|
11
|
+
export interface SyncOptions<TSourceTable extends AnyStoreTable, TTargetTable extends AnyStoreTable, TSourceConnection extends SourceConnection<TSourceTable>, TTargetConnection extends TargetConnection<TTargetTable>> {
|
|
12
|
+
readonly description?: string;
|
|
13
|
+
readonly from: SyncEndpoint<TSourceTable, TSourceConnection>;
|
|
14
|
+
readonly id?: string;
|
|
15
|
+
readonly on?: readonly (AnySignal | string)[];
|
|
16
|
+
readonly to: SyncEndpoint<TTargetTable, TTargetConnection>;
|
|
17
|
+
readonly transform?: SyncTransform<TSourceTable, TTargetTable>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Produce one trail that reads one source entity and writes the transformed
|
|
21
|
+
* result into a target store resource.
|
|
22
|
+
*/
|
|
23
|
+
export declare const sync: <TSourceTable extends AnyStoreTable, TTargetTable extends AnyStoreTable, TSourceConnection extends SourceConnection<TSourceTable>, TTargetConnection extends TargetConnection<TTargetTable>>(options: SyncOptions<TSourceTable, TTargetTable, TSourceConnection, TTargetConnection>) => Trail<IdentityInputOf<TSourceTable>, EntityOf<TTargetTable>>;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/trails/sync.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,SAAS,EACT,QAAQ,EACR,KAAK,EACL,YAAY,EAEb,MAAM,gBAAgB,CAAC;AAGxB,OAAO,KAAK,EACV,aAAa,EACb,QAAQ,EACR,0BAA0B,EAC1B,aAAa,EACb,iBAAiB,EACjB,QAAQ,EACT,MAAM,aAAa,CAAC;AAGrB,KAAK,eAAe,CAAC,MAAM,SAAS,aAAa,IAAI,QAAQ,CAC3D,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC,CACvE,CAAC;AAEF,KAAK,gBAAgB,CAAC,MAAM,SAAS,aAAa,IAAI,QAAQ,CAC5D,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,0BAA0B,CAAC,MAAM,CAAC,CAAC,CAC3D,CAAC;AAEF,KAAK,gBAAgB,CAAC,MAAM,SAAS,aAAa,IAAI,QAAQ,CAC5D,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC,CAC9C,CAAC;AAEF,MAAM,WAAW,YAAY,CAC3B,MAAM,SAAS,aAAa,EAC5B,WAAW,SAAS,gBAAgB,CAAC,MAAM,CAAC,GAAG,gBAAgB,CAAC,MAAM,CAAC;IAEvE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,aAAa,CACvB,YAAY,SAAS,aAAa,EAClC,YAAY,SAAS,aAAa,IAChC,CACF,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC9B,GAAG,EAAE,YAAY,KACd,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC;AAE9D,MAAM,WAAW,WAAW,CAC1B,YAAY,SAAS,aAAa,EAClC,YAAY,SAAS,aAAa,EAClC,iBAAiB,SAAS,gBAAgB,CAAC,YAAY,CAAC,EACxD,iBAAiB,SAAS,gBAAgB,CAAC,YAAY,CAAC;IAExD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC,YAAY,EAAE,iBAAiB,CAAC,CAAC;IAC7D,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,SAAS,GAAG,MAAM,CAAC,EAAE,CAAC;IAC9C,QAAQ,CAAC,EAAE,EAAE,YAAY,CAAC,YAAY,EAAE,iBAAiB,CAAC,CAAC;IAC3D,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;CAChE;AAmGD;;;GAGG;AACH,eAAO,MAAM,IAAI,GACf,YAAY,SAAS,aAAa,EAClC,YAAY,SAAS,aAAa,EAClC,iBAAiB,SAAS,gBAAgB,CAAC,YAAY,CAAC,EACxD,iBAAiB,SAAS,gBAAgB,CAAC,YAAY,CAAC,EAExD,SAAS,WAAW,CAClB,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,iBAAiB,CAClB,KACA,KAAK,CAAC,eAAe,CAAC,YAAY,CAAC,EAAE,QAAQ,CAAC,YAAY,CAAC,CA2E7D,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { InternalError, NotFoundError, Result, trail } from '@ontrails/core';
|
|
2
|
+
import { createTableContour, mapStoreTrailError } from './utils.js';
|
|
3
|
+
const resolveSourceAccessor = (endpoint, ctx) => {
|
|
4
|
+
const connection = endpoint.resource.from(ctx);
|
|
5
|
+
return connection[endpoint.table.name];
|
|
6
|
+
};
|
|
7
|
+
const resolveTargetAccessor = (endpoint, ctx) => {
|
|
8
|
+
const connection = endpoint.resource.from(ctx);
|
|
9
|
+
return connection[endpoint.table.name];
|
|
10
|
+
};
|
|
11
|
+
const sourceMissingError = (table, id) => new NotFoundError(`Store table "${table.name}" could not find source entity "${String(id)}"`);
|
|
12
|
+
const identityInputSchema = (table) => table.schema.pick({
|
|
13
|
+
[table.identity]: true,
|
|
14
|
+
});
|
|
15
|
+
const deriveExamples = (sourceTable, targetTable, transform) => {
|
|
16
|
+
const targetById = new Map();
|
|
17
|
+
for (const fixture of targetTable.fixtures) {
|
|
18
|
+
const id = fixture[targetTable.identity];
|
|
19
|
+
if (id !== undefined) {
|
|
20
|
+
targetById.set(id, fixture);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const examples = sourceTable.fixtures.flatMap((fixture) => {
|
|
24
|
+
const id = fixture[sourceTable.identity];
|
|
25
|
+
if (id === undefined) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
const targetFixture = targetById.get(id) ??
|
|
29
|
+
(transform === undefined && targetTable.schema.safeParse(fixture).success
|
|
30
|
+
? fixture
|
|
31
|
+
: undefined);
|
|
32
|
+
if (targetFixture === undefined) {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
return [
|
|
36
|
+
{
|
|
37
|
+
expected: targetFixture,
|
|
38
|
+
input: { [sourceTable.identity]: id },
|
|
39
|
+
name: `Sync ${targetTable.name} ${String(id)}`,
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
});
|
|
43
|
+
return examples.length === 0 ? undefined : Object.freeze(examples);
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Produce one trail that reads one source entity and writes the transformed
|
|
47
|
+
* result into a target store resource.
|
|
48
|
+
*/
|
|
49
|
+
export const sync = (options) => {
|
|
50
|
+
const id = options.id ?? `${options.to.table.name}.sync`;
|
|
51
|
+
const sourceContour = createTableContour(options.from.table);
|
|
52
|
+
const targetContour = createTableContour(options.to.table);
|
|
53
|
+
return trail(id, {
|
|
54
|
+
// oxlint-disable-next-line max-statements -- sync blaze reads more clearly as one try/catch with schema validation, transform, and accessor call inline
|
|
55
|
+
blaze: async (input, ctx) => {
|
|
56
|
+
try {
|
|
57
|
+
const identifier = input[options.from.table.identity];
|
|
58
|
+
const sourceEntity = await resolveSourceAccessor(options.from, ctx).get(identifier);
|
|
59
|
+
if (sourceEntity === null) {
|
|
60
|
+
return Result.err(sourceMissingError(options.from.table, identifier));
|
|
61
|
+
}
|
|
62
|
+
// No-transform path: the source entity is upserted directly into
|
|
63
|
+
// the target table. The generic signature does not require the two
|
|
64
|
+
// tables to be structurally compatible, so validate `next` against
|
|
65
|
+
// the target table's fixture schema at runtime. This catches
|
|
66
|
+
// accidentally omitted transforms before the underlying store sees
|
|
67
|
+
// a mismatched payload.
|
|
68
|
+
const next = options.transform === undefined
|
|
69
|
+
? options.to.table.fixtureSchema.safeParse(sourceEntity)
|
|
70
|
+
: undefined;
|
|
71
|
+
if (next !== undefined && !next.success) {
|
|
72
|
+
return Result.err(new InternalError(`${id} produced an invalid target entity: ${next.error.message}`));
|
|
73
|
+
}
|
|
74
|
+
const payload = options.transform === undefined
|
|
75
|
+
? next?.data
|
|
76
|
+
: await options.transform(sourceEntity, ctx);
|
|
77
|
+
const synced = await resolveTargetAccessor(options.to, ctx).upsert(payload);
|
|
78
|
+
return Result.ok(synced);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
return Result.err(mapStoreTrailError(id, error));
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
contours: [sourceContour, targetContour],
|
|
85
|
+
description: options.description ??
|
|
86
|
+
`Sync one "${options.from.table.name}" entity into "${options.to.table.name}".`,
|
|
87
|
+
examples: deriveExamples(options.from.table, options.to.table, options.transform),
|
|
88
|
+
input: identityInputSchema(options.from.table),
|
|
89
|
+
intent: 'write',
|
|
90
|
+
on: options.on,
|
|
91
|
+
output: options.to.table.schema,
|
|
92
|
+
pattern: 'sync',
|
|
93
|
+
resources: [options.from.resource, options.to.resource],
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/trails/sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Contour } from '@ontrails/core';
|
|
2
|
+
import type { z } from 'zod';
|
|
3
|
+
import type { AnyStoreTable } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* The contour type produced by {@link createTableContour} for a given store
|
|
6
|
+
* table. Threads the table's name, schema shape, and identity through the
|
|
7
|
+
* contour generics so downstream `deriveTrail()` calls project concrete
|
|
8
|
+
* input/output types instead of widening back to
|
|
9
|
+
* `Contour<string, z.ZodRawShape, string>` (the `AnyContour` alias).
|
|
10
|
+
*/
|
|
11
|
+
export type TableContour<TTable extends AnyStoreTable> = Contour<TTable['name'], TTable['schema']['shape'], Extract<TTable['identity'], keyof TTable['schema']['shape'] & string>>;
|
|
12
|
+
/**
|
|
13
|
+
* Build the shape used when deriving a contour view of a store table.
|
|
14
|
+
*
|
|
15
|
+
* Contour validates every example against the shape passed in, so the shape
|
|
16
|
+
* must match how fixtures are actually shaped. Store fixtures may omit
|
|
17
|
+
* framework-generated fields (`createdAt`, `version`, ...) because the
|
|
18
|
+
* connector populates them, so we mirror `fixtureSchema`'s treatment of
|
|
19
|
+
* generated fields: generated, non-identity fields are made optional; the
|
|
20
|
+
* identity field stays required because read/delete/update all derive their
|
|
21
|
+
* input from it. Previously reconcile.ts and sync.ts passed
|
|
22
|
+
* `table.schema.shape` directly and crashed when a fixture omitted
|
|
23
|
+
* `createdAt` or another generated field.
|
|
24
|
+
*/
|
|
25
|
+
export declare const buildContourShape: (table: AnyStoreTable) => Record<string, z.ZodType>;
|
|
26
|
+
/**
|
|
27
|
+
* Derive a contour view of a store table.
|
|
28
|
+
*
|
|
29
|
+
* Both `sync` and `reconcile` use this helper so they pick up the
|
|
30
|
+
* fixture-shape treatment (generated fields optional).
|
|
31
|
+
*
|
|
32
|
+
* @remarks
|
|
33
|
+
* Intentionally not cached. `contour()` brands the identity schema via
|
|
34
|
+
* `Object.defineProperty(..., { writable: false })`, and re-invoking on a
|
|
35
|
+
* schema that's already been branded throws TypeError. Factory call sites
|
|
36
|
+
* already build the contour once per trail instance, so rebuilding on a
|
|
37
|
+
* warm call is cheap and side-effect-free.
|
|
38
|
+
*/
|
|
39
|
+
export declare const createTableContour: <TTable extends AnyStoreTable>(table: TTable) => TableContour<TTable>;
|
|
40
|
+
/**
|
|
41
|
+
* Coerce an unknown thrown value into an Error instance, preserving the
|
|
42
|
+
* original when possible.
|
|
43
|
+
*/
|
|
44
|
+
export declare const asError: (error: unknown) => Error;
|
|
45
|
+
/**
|
|
46
|
+
* Map a caught error into a `TrailsError` suitable for surfacing from a store
|
|
47
|
+
* trail factory. Pass-through for errors already in the taxonomy; otherwise
|
|
48
|
+
* wrap in an `InternalError` keyed by the trail id.
|
|
49
|
+
*/
|
|
50
|
+
export declare const mapStoreTrailError: (trailId: string, error: unknown) => Error;
|
|
51
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/trails/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,CAAC,MAAM,SAAS,aAAa,IAAI,OAAO,CAC9D,MAAM,CAAC,MAAM,CAAC,EACd,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EACzB,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,GAAG,MAAM,CAAC,CACtE,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iBAAiB,GAC5B,OAAO,aAAa,KACnB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAiB1B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,SAAS,aAAa,EAC7D,OAAO,MAAM,KACZ,YAAY,CAAC,MAAM,CAIM,CAAC;AAE7B;;;GAGG;AACH,eAAO,MAAM,OAAO,GAAI,OAAO,OAAO,KAAG,KACkB,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,MAAM,EAAE,OAAO,OAAO,KAAG,KASpE,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { InternalError, contour, isTrailsError } from '@ontrails/core';
|
|
2
|
+
/**
|
|
3
|
+
* Build the shape used when deriving a contour view of a store table.
|
|
4
|
+
*
|
|
5
|
+
* Contour validates every example against the shape passed in, so the shape
|
|
6
|
+
* must match how fixtures are actually shaped. Store fixtures may omit
|
|
7
|
+
* framework-generated fields (`createdAt`, `version`, ...) because the
|
|
8
|
+
* connector populates them, so we mirror `fixtureSchema`'s treatment of
|
|
9
|
+
* generated fields: generated, non-identity fields are made optional; the
|
|
10
|
+
* identity field stays required because read/delete/update all derive their
|
|
11
|
+
* input from it. Previously reconcile.ts and sync.ts passed
|
|
12
|
+
* `table.schema.shape` directly and crashed when a fixture omitted
|
|
13
|
+
* `createdAt` or another generated field.
|
|
14
|
+
*/
|
|
15
|
+
export const buildContourShape = (table) => {
|
|
16
|
+
const shape = table.schema.shape;
|
|
17
|
+
const generatedNonIdentity = new Set(table.generated.filter((field) => field !== table.identity));
|
|
18
|
+
if (generatedNonIdentity.size === 0) {
|
|
19
|
+
return shape;
|
|
20
|
+
}
|
|
21
|
+
const next = {};
|
|
22
|
+
for (const [field, fieldSchema] of Object.entries(shape)) {
|
|
23
|
+
next[field] = generatedNonIdentity.has(field)
|
|
24
|
+
? fieldSchema.optional()
|
|
25
|
+
: fieldSchema;
|
|
26
|
+
}
|
|
27
|
+
return next;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Derive a contour view of a store table.
|
|
31
|
+
*
|
|
32
|
+
* Both `sync` and `reconcile` use this helper so they pick up the
|
|
33
|
+
* fixture-shape treatment (generated fields optional).
|
|
34
|
+
*
|
|
35
|
+
* @remarks
|
|
36
|
+
* Intentionally not cached. `contour()` brands the identity schema via
|
|
37
|
+
* `Object.defineProperty(..., { writable: false })`, and re-invoking on a
|
|
38
|
+
* schema that's already been branded throws TypeError. Factory call sites
|
|
39
|
+
* already build the contour once per trail instance, so rebuilding on a
|
|
40
|
+
* warm call is cheap and side-effect-free.
|
|
41
|
+
*/
|
|
42
|
+
export const createTableContour = (table) => contour(table.name, buildContourShape(table), {
|
|
43
|
+
examples: table.fixtures,
|
|
44
|
+
identity: table.identity,
|
|
45
|
+
});
|
|
46
|
+
/**
|
|
47
|
+
* Coerce an unknown thrown value into an Error instance, preserving the
|
|
48
|
+
* original when possible.
|
|
49
|
+
*/
|
|
50
|
+
export const asError = (error) => error instanceof Error ? error : new Error(String(error));
|
|
51
|
+
/**
|
|
52
|
+
* Map a caught error into a `TrailsError` suitable for surfacing from a store
|
|
53
|
+
* trail factory. Pass-through for errors already in the taxonomy; otherwise
|
|
54
|
+
* wrap in an `InternalError` keyed by the trail id.
|
|
55
|
+
*/
|
|
56
|
+
export const mapStoreTrailError = (trailId, error) => {
|
|
57
|
+
if (isTrailsError(error)) {
|
|
58
|
+
return error;
|
|
59
|
+
}
|
|
60
|
+
const resolved = asError(error);
|
|
61
|
+
return new InternalError(`${trailId} failed: ${resolved.message}`, {
|
|
62
|
+
cause: resolved,
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/trails/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAmBvE;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,KAAoB,EACO,EAAE;IAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,KAA6C,CAAC;IACzE,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAClC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,KAAK,CAAC,QAAQ,CAAC,CAC5D,CAAC;IAEF,IAAI,oBAAoB,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACpC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,IAAI,GAA8B,EAAE,CAAC;IAC3C,KAAK,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzD,IAAI,CAAC,KAAK,CAAC,GAAG,oBAAoB,CAAC,GAAG,CAAC,KAAK,CAAC;YAC3C,CAAC,CAAC,WAAW,CAAC,QAAQ,EAAE;YACxB,CAAC,CAAC,WAAW,CAAC;IAClB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,KAAa,EACS,EAAE,CACxB,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,iBAAiB,CAAC,KAAK,CAAC,EAAE;IAC5C,QAAQ,EAAE,KAAK,CAAC,QAA8C;IAC9D,QAAQ,EAAE,KAAK,CAAC,QAAQ;CACzB,CAAyB,CAAC;AAE7B;;;GAGG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,KAAc,EAAS,EAAE,CAC/C,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AAE5D;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,OAAe,EAAE,KAAc,EAAS,EAAE;IAC3E,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAChC,OAAO,IAAI,aAAa,CAAC,GAAG,OAAO,YAAY,QAAQ,CAAC,OAAO,EAAE,EAAE;QACjE,KAAK,EAAE,QAAQ;KAChB,CAAC,CAAC;AACL,CAAC,CAAC"}
|