ignotum 0.0.2 → 0.0.4
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/README.md +23 -7
- package/dist/cli/bin.mjs +32313 -751
- package/dist/cli/bin.mjs.map +1 -1
- package/dist/runtime/{api-Cpx57mk3.d.ts → api-D2bsAz4T.d.ts} +3 -2
- package/dist/runtime/{api-BXXIZc_Q.js → api-DtX8qPrq.js} +58 -27
- package/dist/runtime/api-DtX8qPrq.js.map +1 -0
- package/dist/runtime/client.d.ts +11 -4
- package/dist/runtime/client.js +343 -79
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/{result-B2W-z2wG.js → descriptor-t6BOEGw9-BTHR-ZMv.js} +119 -4
- package/dist/runtime/descriptor-t6BOEGw9-BTHR-ZMv.js.map +1 -0
- package/dist/runtime/id-Btwac71X-B9OeBzvq.d.ts +9 -0
- package/dist/runtime/id-D570vudg.js +26 -0
- package/dist/runtime/id-D570vudg.js.map +1 -0
- package/dist/runtime/{index-BgWROoyk.d.ts → index-Dl_VQGmA.d.ts} +114 -31
- package/dist/runtime/internal/api.d.ts +1 -1
- package/dist/runtime/internal/api.js +1 -1
- package/dist/runtime/internal/host.d.ts +44 -0
- package/dist/runtime/internal/host.js +213 -0
- package/dist/runtime/internal/host.js.map +1 -0
- package/dist/runtime/internal/server.d.ts +1 -1
- package/dist/runtime/internal/server.js +1 -1
- package/dist/runtime/internal/types.d.ts +1 -1
- package/dist/runtime/internal/types.js +1 -1
- package/dist/runtime/{result-C1ZdsM6Y.d.ts → result-BgcHn25t.d.ts} +21 -6
- package/dist/runtime/schema-B-jMZEbs.js +227 -0
- package/dist/runtime/schema-B-jMZEbs.js.map +1 -0
- package/dist/runtime/server.d.ts +2 -2
- package/dist/runtime/server.js +2 -2
- package/package.json +15 -5
- package/src/cli/agent-files.ts +6 -2
- package/src/cli/app-configuration.ts +135 -0
- package/src/cli/bin.ts +24 -1
- package/src/cli/build/artifact.ts +95 -0
- package/src/cli/build/client.ts +103 -0
- package/src/cli/build/server.ts +526 -0
- package/src/cli/client-config.ts +141 -0
- package/src/cli/client-entry.ts +152 -0
- package/src/cli/client-plugin.ts +86 -16
- package/src/cli/codegen.ts +5 -5
- package/src/cli/command.ts +41 -9
- package/src/cli/control-client.ts +343 -0
- package/src/cli/deploy.ts +217 -0
- package/src/cli/dev.ts +43 -108
- package/src/cli/module-boundaries.ts +183 -0
- package/src/cli/{new-project.ts → new-app.ts} +86 -86
- package/src/cli/package-manager.ts +9 -9
- package/src/client/app.ts +15 -0
- package/src/client/errors.ts +1 -17
- package/src/client/index.ts +2 -0
- package/src/client/sync.ts +172 -120
- package/src/dev-runtime/database.ts +253 -96
- package/src/dev-runtime/dev-database.ts +14 -14
- package/src/dev-runtime/functions.ts +57 -88
- package/src/dev-runtime/id.ts +2 -10
- package/src/dev-runtime/migrations.ts +68 -0
- package/src/dev-runtime/sync.ts +266 -83
- package/src/internal/function-definition.ts +77 -0
- package/src/internal/host.ts +1 -0
- package/src/internal/http-paths.ts +3 -1
- package/dist/runtime/api-BXXIZc_Q.js.map +0 -1
- package/dist/runtime/result-B2W-z2wG.js.map +0 -1
- package/dist/runtime/schema-CNEVLF7D.js +0 -116
- package/dist/runtime/schema-CNEVLF7D.js.map +0 -1
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { Context, DateTime, Effect, Layer, Option, Schema } from "effect";
|
|
2
2
|
import { SqlClient, SqlError, SqlSchema } from "effect/unstable/sql";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
GeneratedId,
|
|
5
|
+
TableId,
|
|
6
|
+
type GeneratedId as GeneratedIdType,
|
|
7
|
+
} from "@ignotum/contracts/runtime/id";
|
|
8
|
+
import { IdGenerator } from "@ignotum/shared/id";
|
|
9
|
+
import {
|
|
10
|
+
AppStateRevision,
|
|
11
|
+
dependencyKey,
|
|
12
|
+
documentDependency,
|
|
13
|
+
tableDependency,
|
|
14
|
+
type DependencyKey,
|
|
15
|
+
} from "@ignotum/contracts/runtime/hosted";
|
|
4
16
|
import {
|
|
5
17
|
documentNotFound,
|
|
6
18
|
resultFromEffect,
|
|
@@ -12,7 +24,8 @@ import type {
|
|
|
12
24
|
RuntimeTableDefinition,
|
|
13
25
|
} from "@ignotum/contracts/runtime/schema";
|
|
14
26
|
import { SchemaDefinitionTypeId } from "@ignotum/contracts/schema";
|
|
15
|
-
|
|
27
|
+
|
|
28
|
+
import { DevelopmentDatabase } from "./migrations.js";
|
|
16
29
|
|
|
17
30
|
interface RuntimeObject {
|
|
18
31
|
readonly [fieldName: string]: RuntimeDocumentValue | undefined;
|
|
@@ -58,32 +71,78 @@ export interface LocalMutationContext {
|
|
|
58
71
|
}
|
|
59
72
|
|
|
60
73
|
interface LocalDatabaseService {
|
|
74
|
+
readonly revision: Effect.Effect<AppStateRevision>;
|
|
61
75
|
readonly queryTransaction: <Success, Failure>(
|
|
62
76
|
schema: RuntimeSchemaDefinition,
|
|
63
77
|
use: (context: Readonly<LocalQueryContext>) => Effect.Effect<Success, Failure>,
|
|
64
78
|
) => Effect.Effect<Success, Failure | SqlError.SqlError>;
|
|
79
|
+
readonly trackedQueryTransaction: <Success, Failure>(
|
|
80
|
+
schema: RuntimeSchemaDefinition,
|
|
81
|
+
use: (context: Readonly<LocalQueryContext>) => Effect.Effect<Success, Failure>,
|
|
82
|
+
) => Effect.Effect<
|
|
83
|
+
{
|
|
84
|
+
readonly value: Success;
|
|
85
|
+
readonly dependencies: ReadonlyArray<DependencyKey>;
|
|
86
|
+
readonly observedRevision: AppStateRevision;
|
|
87
|
+
},
|
|
88
|
+
Failure | SqlError.SqlError
|
|
89
|
+
>;
|
|
65
90
|
readonly mutationTransaction: <Success, Failure>(
|
|
66
91
|
schema: RuntimeSchemaDefinition,
|
|
67
92
|
use: (context: Readonly<LocalMutationContext>) => Effect.Effect<Success, Failure>,
|
|
68
93
|
) => Effect.Effect<Success, Failure | SqlError.SqlError>;
|
|
94
|
+
readonly trackedMutationTransaction: <Success, Failure>(
|
|
95
|
+
schema: RuntimeSchemaDefinition,
|
|
96
|
+
use: (context: Readonly<LocalMutationContext>) => Effect.Effect<Success, Failure>,
|
|
97
|
+
) => Effect.Effect<
|
|
98
|
+
{
|
|
99
|
+
readonly value: Success;
|
|
100
|
+
readonly invalidations: ReadonlyArray<DependencyKey>;
|
|
101
|
+
readonly committedRevision: AppStateRevision;
|
|
102
|
+
},
|
|
103
|
+
Failure | SqlError.SqlError
|
|
104
|
+
>;
|
|
69
105
|
}
|
|
70
106
|
|
|
71
107
|
const StoredDocument = Schema.Struct({
|
|
72
108
|
id: GeneratedId,
|
|
73
|
-
tableName: Schema.String,
|
|
74
109
|
createdAt: Schema.Int,
|
|
75
110
|
updatedAt: Schema.Int,
|
|
76
111
|
fields: Schema.String,
|
|
77
112
|
});
|
|
78
113
|
type StoredDocument = typeof StoredDocument.Type;
|
|
79
114
|
|
|
115
|
+
const StoredTable = Schema.Struct({ id: TableId, name: Schema.String });
|
|
116
|
+
type StoredTable = typeof StoredTable.Type;
|
|
117
|
+
interface ResolvedTable extends StoredTable {
|
|
118
|
+
readonly definition: RuntimeTableDefinition;
|
|
119
|
+
}
|
|
120
|
+
|
|
80
121
|
const DocumentLookup = Schema.Struct({
|
|
81
122
|
id: Schema.String,
|
|
82
|
-
|
|
123
|
+
tableId: TableId,
|
|
83
124
|
});
|
|
84
|
-
const TableLookup = Schema.Struct({
|
|
125
|
+
const TableLookup = Schema.Struct({ tableId: TableId });
|
|
126
|
+
const TableNameLookup = Schema.Struct({ name: Schema.String });
|
|
127
|
+
const TableInsert = Schema.Struct({ id: TableId, name: Schema.String });
|
|
128
|
+
const RevisionRow = Schema.Struct({ revision: AppStateRevision });
|
|
85
129
|
type DatabaseOperation = "collect" | "get" | "insert" | "patch" | "replace";
|
|
86
130
|
|
|
131
|
+
interface DependencyRecorder {
|
|
132
|
+
readonly record: (...dependencies: ReadonlyArray<DependencyKey>) => void;
|
|
133
|
+
readonly values: () => ReadonlyArray<DependencyKey>;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const makeDependencyRecorder = (): DependencyRecorder => {
|
|
137
|
+
const dependencies = new Map<string, DependencyKey>();
|
|
138
|
+
return {
|
|
139
|
+
record: (...added) => {
|
|
140
|
+
for (const dependency of added) dependencies.set(dependencyKey(dependency), dependency);
|
|
141
|
+
},
|
|
142
|
+
values: () => globalThis.Array.from(dependencies.values()),
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
|
|
87
146
|
export class UnknownDatabaseTable extends Schema.TaggedError<UnknownDatabaseTable>()(
|
|
88
147
|
"UnknownDatabaseTable",
|
|
89
148
|
{
|
|
@@ -139,37 +198,37 @@ const fieldsCodec = (table: RuntimeTableDefinition) =>
|
|
|
139
198
|
);
|
|
140
199
|
|
|
141
200
|
const encodeFields = Effect.fn("LocalDatabase.encodeFields")(function* (
|
|
142
|
-
|
|
201
|
+
table: RuntimeTableDefinition,
|
|
143
202
|
tableName: string,
|
|
144
203
|
value: RuntimeValue,
|
|
145
204
|
operation: Extract<DatabaseOperation, "insert" | "patch" | "replace">,
|
|
146
205
|
id: string | null,
|
|
147
206
|
) {
|
|
148
|
-
const table = yield* tableFor(schema, tableName);
|
|
149
207
|
return yield* Schema.encodeEffect(fieldsCodec(table), {
|
|
150
208
|
onExcessProperty: "error",
|
|
151
209
|
})(value).pipe(Effect.mapError((cause) => schemaMismatch(tableName, id, operation, cause)));
|
|
152
210
|
});
|
|
153
211
|
|
|
154
212
|
const decodeFields = Effect.fn("LocalDatabase.decodeFields")(function* (
|
|
155
|
-
|
|
213
|
+
table: RuntimeTableDefinition,
|
|
214
|
+
tableName: string,
|
|
156
215
|
row: StoredDocument,
|
|
157
216
|
operation: Extract<DatabaseOperation, "collect" | "get" | "patch">,
|
|
158
217
|
) {
|
|
159
|
-
const table = yield* tableFor(schema, row.tableName);
|
|
160
218
|
return yield* Schema.decodeEffect(fieldsCodec(table), {
|
|
161
219
|
onExcessProperty: "error",
|
|
162
220
|
})(row.fields).pipe(
|
|
163
|
-
Effect.mapError((cause) => schemaMismatch(
|
|
221
|
+
Effect.mapError((cause) => schemaMismatch(tableName, row.id, operation, cause)),
|
|
164
222
|
);
|
|
165
223
|
});
|
|
166
224
|
|
|
167
225
|
const toRuntimeDocument = Effect.fn("LocalDatabase.toRuntimeDocument")(function* (
|
|
168
|
-
|
|
226
|
+
table: RuntimeTableDefinition,
|
|
227
|
+
tableName: string,
|
|
169
228
|
row: StoredDocument,
|
|
170
229
|
operation: Extract<DatabaseOperation, "collect" | "get">,
|
|
171
230
|
) {
|
|
172
|
-
const fields = yield* decodeFields(
|
|
231
|
+
const fields = yield* decodeFields(table, tableName, row, operation);
|
|
173
232
|
return {
|
|
174
233
|
...fields,
|
|
175
234
|
id: row.id,
|
|
@@ -188,76 +247,125 @@ export class LocalDatabase extends Context.Service<LocalDatabase, LocalDatabaseS
|
|
|
188
247
|
LocalDatabase,
|
|
189
248
|
Effect.gen(function* () {
|
|
190
249
|
const ids = yield* IdGenerator;
|
|
250
|
+
yield* DevelopmentDatabase;
|
|
191
251
|
const sql = yield* SqlClient.SqlClient;
|
|
192
252
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
253
|
+
const findTable = SqlSchema.findOneOption({
|
|
254
|
+
Request: TableNameLookup,
|
|
255
|
+
Result: StoredTable,
|
|
256
|
+
execute: ({ name }) => sql`
|
|
257
|
+
SELECT id, name
|
|
258
|
+
FROM tables
|
|
259
|
+
WHERE name = ${name}
|
|
260
|
+
LIMIT 1
|
|
261
|
+
`,
|
|
262
|
+
});
|
|
263
|
+
const insertTable = SqlSchema.findOne({
|
|
264
|
+
Request: TableInsert,
|
|
265
|
+
Result: StoredTable,
|
|
266
|
+
execute: ({ id, name }) => sql`
|
|
267
|
+
INSERT INTO tables (id, name)
|
|
268
|
+
VALUES (${id}, ${name})
|
|
269
|
+
ON CONFLICT (name) DO UPDATE SET name = excluded.name
|
|
270
|
+
RETURNING id, name
|
|
271
|
+
`,
|
|
272
|
+
});
|
|
207
273
|
|
|
208
274
|
const findStored = SqlSchema.findOneOption({
|
|
209
275
|
Request: DocumentLookup,
|
|
210
276
|
Result: StoredDocument,
|
|
211
|
-
execute: ({ id,
|
|
212
|
-
SELECT id,
|
|
277
|
+
execute: ({ id, tableId }) => sql`
|
|
278
|
+
SELECT id, createdAt, updatedAt, fields
|
|
213
279
|
FROM documents
|
|
214
|
-
WHERE id = ${id} AND
|
|
280
|
+
WHERE id = ${id} AND tableId = ${tableId}
|
|
215
281
|
LIMIT 1
|
|
216
282
|
`,
|
|
217
283
|
});
|
|
218
284
|
const collectStored = SqlSchema.findAll({
|
|
219
285
|
Request: TableLookup,
|
|
220
286
|
Result: StoredDocument,
|
|
221
|
-
execute: ({
|
|
222
|
-
SELECT id,
|
|
287
|
+
execute: ({ tableId }) => sql`
|
|
288
|
+
SELECT id, createdAt, updatedAt, fields
|
|
223
289
|
FROM documents
|
|
224
|
-
WHERE
|
|
290
|
+
WHERE tableId = ${tableId}
|
|
225
291
|
ORDER BY createdAt ASC, id ASC
|
|
226
292
|
`,
|
|
227
293
|
});
|
|
294
|
+
const currentRevision = SqlSchema.findOne({
|
|
295
|
+
Request: Schema.Void,
|
|
296
|
+
Result: RevisionRow,
|
|
297
|
+
execute: () => sql`SELECT revision FROM app_state WHERE singleton = 1`,
|
|
298
|
+
});
|
|
299
|
+
const advanceRevision = SqlSchema.findOne({
|
|
300
|
+
Request: Schema.Void,
|
|
301
|
+
Result: RevisionRow,
|
|
302
|
+
execute: () => sql`
|
|
303
|
+
UPDATE app_state
|
|
304
|
+
SET revision = revision + 1
|
|
305
|
+
WHERE singleton = 1
|
|
306
|
+
RETURNING revision
|
|
307
|
+
`,
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const resolveTable = Effect.fn("LocalDatabase.resolveTable")(function* (
|
|
311
|
+
schema: RuntimeSchemaDefinition,
|
|
312
|
+
name: string,
|
|
313
|
+
): Effect.fn.Return<
|
|
314
|
+
ResolvedTable,
|
|
315
|
+
UnknownDatabaseTable | SqlError.SqlError | Schema.SchemaError
|
|
316
|
+
> {
|
|
317
|
+
const definition = yield* tableFor(schema, name);
|
|
318
|
+
const existing = yield* findTable({ name });
|
|
319
|
+
if (Option.isSome(existing)) return { ...existing.value, definition };
|
|
320
|
+
const id = yield* ids.generate(TableId);
|
|
321
|
+
const stored = yield* insertTable({ id, name }).pipe(
|
|
322
|
+
Effect.catchTags({ NoSuchElementError: Effect.die }),
|
|
323
|
+
);
|
|
324
|
+
return { ...stored, definition };
|
|
325
|
+
});
|
|
228
326
|
|
|
229
327
|
const find = Effect.fn("LocalDatabase.find")(function* (
|
|
230
328
|
schema: RuntimeSchemaDefinition,
|
|
231
329
|
tableName: string,
|
|
232
330
|
id: string,
|
|
331
|
+
dependencies?: DependencyRecorder,
|
|
233
332
|
) {
|
|
234
|
-
yield*
|
|
235
|
-
const
|
|
333
|
+
const table = yield* resolveTable(schema, tableName);
|
|
334
|
+
const documentId = GeneratedId.make(id);
|
|
335
|
+
dependencies?.record(documentDependency(table.id, documentId));
|
|
336
|
+
const row = yield* findStored({ id, tableId: table.id });
|
|
236
337
|
if (Option.isNone(row)) return undefined;
|
|
237
|
-
return yield* toRuntimeDocument(
|
|
338
|
+
return yield* toRuntimeDocument(table.definition, tableName, row.value, "get");
|
|
238
339
|
});
|
|
239
340
|
|
|
240
341
|
const collect = Effect.fn("LocalDatabase.collect")(function* (
|
|
241
342
|
schema: RuntimeSchemaDefinition,
|
|
242
343
|
tableName: string,
|
|
344
|
+
dependencies?: DependencyRecorder,
|
|
243
345
|
) {
|
|
244
|
-
yield*
|
|
245
|
-
|
|
246
|
-
|
|
346
|
+
const table = yield* resolveTable(schema, tableName);
|
|
347
|
+
dependencies?.record(tableDependency(table.id));
|
|
348
|
+
const rows = yield* collectStored({ tableId: table.id });
|
|
349
|
+
return yield* Effect.forEach(rows, (row) =>
|
|
350
|
+
toRuntimeDocument(table.definition, tableName, row, "collect"),
|
|
351
|
+
);
|
|
247
352
|
});
|
|
248
353
|
|
|
249
354
|
const insert = Effect.fn("LocalDatabase.insert")(function* (
|
|
250
355
|
schema: RuntimeSchemaDefinition,
|
|
251
356
|
tableName: string,
|
|
252
357
|
value: RuntimeValue,
|
|
358
|
+
invalidations: DependencyRecorder,
|
|
253
359
|
) {
|
|
254
|
-
const
|
|
255
|
-
const
|
|
360
|
+
const table = yield* resolveTable(schema, tableName);
|
|
361
|
+
const fields = yield* encodeFields(table.definition, tableName, value, "insert", null);
|
|
362
|
+
const id = yield* ids.generate(GeneratedId);
|
|
256
363
|
const now = DateTime.toEpochMillis(yield* DateTime.now);
|
|
257
364
|
yield* sql`
|
|
258
|
-
INSERT INTO documents (id,
|
|
259
|
-
VALUES (${id}, ${
|
|
365
|
+
INSERT INTO documents (id, tableId, createdAt, updatedAt, fields)
|
|
366
|
+
VALUES (${id}, ${table.id}, ${now}, ${now}, ${fields})
|
|
260
367
|
`;
|
|
368
|
+
invalidations.record(tableDependency(table.id), documentDependency(table.id, id));
|
|
261
369
|
return id;
|
|
262
370
|
});
|
|
263
371
|
|
|
@@ -265,9 +373,12 @@ export class LocalDatabase extends Context.Service<LocalDatabase, LocalDatabaseS
|
|
|
265
373
|
schema: RuntimeSchemaDefinition,
|
|
266
374
|
tableName: string,
|
|
267
375
|
id: string,
|
|
376
|
+
invalidations: DependencyRecorder,
|
|
268
377
|
) {
|
|
269
|
-
yield*
|
|
270
|
-
|
|
378
|
+
const table = yield* resolveTable(schema, tableName);
|
|
379
|
+
const documentId = GeneratedId.make(id);
|
|
380
|
+
yield* sql`DELETE FROM documents WHERE id = ${id} AND tableId = ${table.id}`;
|
|
381
|
+
invalidations.record(tableDependency(table.id), documentDependency(table.id, documentId));
|
|
271
382
|
});
|
|
272
383
|
|
|
273
384
|
const patch = Effect.fn("LocalDatabase.patch")(function* (
|
|
@@ -275,24 +386,28 @@ export class LocalDatabase extends Context.Service<LocalDatabase, LocalDatabaseS
|
|
|
275
386
|
tableName: string,
|
|
276
387
|
id: string,
|
|
277
388
|
value: RuntimeValue,
|
|
389
|
+
invalidations: DependencyRecorder,
|
|
278
390
|
) {
|
|
279
|
-
yield*
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
391
|
+
const table = yield* resolveTable(schema, tableName);
|
|
392
|
+
const documentId = GeneratedId.make(id);
|
|
393
|
+
const stored = yield* findStored({ id, tableId: table.id });
|
|
394
|
+
if (Option.isSome(stored)) {
|
|
395
|
+
const current = yield* decodeFields(table.definition, tableName, stored.value, "patch");
|
|
396
|
+
const fields = yield* encodeFields(
|
|
397
|
+
table.definition,
|
|
398
|
+
tableName,
|
|
399
|
+
{ ...current, ...value },
|
|
400
|
+
"patch",
|
|
401
|
+
id,
|
|
402
|
+
);
|
|
403
|
+
const updatedAt = nextUpdatedAt(yield* DateTime.now, stored.value.updatedAt);
|
|
404
|
+
yield* sql`
|
|
405
|
+
UPDATE documents
|
|
406
|
+
SET fields = ${fields}, updatedAt = ${updatedAt}
|
|
407
|
+
WHERE id = ${id} AND tableId = ${table.id}
|
|
408
|
+
`;
|
|
409
|
+
}
|
|
410
|
+
invalidations.record(tableDependency(table.id), documentDependency(table.id, documentId));
|
|
296
411
|
});
|
|
297
412
|
|
|
298
413
|
const replace = Effect.fn("LocalDatabase.replace")(function* (
|
|
@@ -300,26 +415,33 @@ export class LocalDatabase extends Context.Service<LocalDatabase, LocalDatabaseS
|
|
|
300
415
|
tableName: string,
|
|
301
416
|
id: string,
|
|
302
417
|
value: RuntimeValue,
|
|
418
|
+
invalidations: DependencyRecorder,
|
|
303
419
|
) {
|
|
304
|
-
yield*
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
420
|
+
const table = yield* resolveTable(schema, tableName);
|
|
421
|
+
const documentId = GeneratedId.make(id);
|
|
422
|
+
const stored = yield* findStored({ id, tableId: table.id });
|
|
423
|
+
if (Option.isSome(stored)) {
|
|
424
|
+
const fields = yield* encodeFields(table.definition, tableName, value, "replace", id);
|
|
425
|
+
const updatedAt = nextUpdatedAt(yield* DateTime.now, stored.value.updatedAt);
|
|
426
|
+
yield* sql`
|
|
427
|
+
UPDATE documents
|
|
428
|
+
SET fields = ${fields}, updatedAt = ${updatedAt}
|
|
429
|
+
WHERE id = ${id} AND tableId = ${table.id}
|
|
430
|
+
`;
|
|
431
|
+
}
|
|
432
|
+
invalidations.record(tableDependency(table.id), documentDependency(table.id, documentId));
|
|
314
433
|
});
|
|
315
434
|
|
|
316
|
-
const makeReader = (
|
|
435
|
+
const makeReader = (
|
|
436
|
+
schema: RuntimeSchemaDefinition,
|
|
437
|
+
dependencies?: DependencyRecorder,
|
|
438
|
+
): LocalDatabaseReader =>
|
|
317
439
|
Object.freeze({
|
|
318
440
|
find: (tableName: string, id: string) =>
|
|
319
|
-
resultFromEffect(find(schema, tableName, id).pipe(Effect.orDie)),
|
|
441
|
+
resultFromEffect(find(schema, tableName, id, dependencies).pipe(Effect.orDie)),
|
|
320
442
|
get: (tableName: string, id: string) =>
|
|
321
443
|
resultFromEffect(
|
|
322
|
-
find(schema, tableName, id).pipe(
|
|
444
|
+
find(schema, tableName, id, dependencies).pipe(
|
|
323
445
|
Effect.orDie,
|
|
324
446
|
Effect.flatMap((document) =>
|
|
325
447
|
document === undefined
|
|
@@ -330,44 +452,79 @@ export class LocalDatabase extends Context.Service<LocalDatabase, LocalDatabaseS
|
|
|
330
452
|
),
|
|
331
453
|
query: (tableName: string) =>
|
|
332
454
|
Object.freeze({
|
|
333
|
-
collect: () =>
|
|
455
|
+
collect: () =>
|
|
456
|
+
resultFromEffect(collect(schema, tableName, dependencies).pipe(Effect.orDie)),
|
|
334
457
|
}),
|
|
335
458
|
});
|
|
336
459
|
|
|
337
|
-
const makeWriter = (
|
|
460
|
+
const makeWriter = (
|
|
461
|
+
schema: RuntimeSchemaDefinition,
|
|
462
|
+
invalidations: DependencyRecorder,
|
|
463
|
+
): LocalDatabaseWriter => {
|
|
338
464
|
const reader = makeReader(schema);
|
|
339
465
|
return Object.freeze({
|
|
340
466
|
...reader,
|
|
341
467
|
delete: (tableName: string, id: string) =>
|
|
342
|
-
resultFromEffect(
|
|
468
|
+
resultFromEffect(
|
|
469
|
+
deleteDocument(schema, tableName, id, invalidations).pipe(Effect.orDie),
|
|
470
|
+
),
|
|
343
471
|
insert: (tableName: string, value: RuntimeValue) =>
|
|
344
|
-
resultFromEffect(insert(schema, tableName, value).pipe(Effect.orDie)),
|
|
472
|
+
resultFromEffect(insert(schema, tableName, value, invalidations).pipe(Effect.orDie)),
|
|
345
473
|
patch: (tableName: string, id: string, value: RuntimeValue) =>
|
|
346
|
-
resultFromEffect(patch(schema, tableName, id, value).pipe(Effect.orDie)),
|
|
474
|
+
resultFromEffect(patch(schema, tableName, id, value, invalidations).pipe(Effect.orDie)),
|
|
347
475
|
replace: (tableName: string, id: string, value: RuntimeValue) =>
|
|
348
|
-
resultFromEffect(
|
|
476
|
+
resultFromEffect(
|
|
477
|
+
replace(schema, tableName, id, value, invalidations).pipe(Effect.orDie),
|
|
478
|
+
),
|
|
349
479
|
});
|
|
350
480
|
};
|
|
351
481
|
|
|
482
|
+
const trackedQueryTransaction: LocalDatabaseService["trackedQueryTransaction"] = (
|
|
483
|
+
schema,
|
|
484
|
+
use,
|
|
485
|
+
) =>
|
|
486
|
+
sql.withTransaction(
|
|
487
|
+
Effect.gen(function* () {
|
|
488
|
+
const dependencies = makeDependencyRecorder();
|
|
489
|
+
const context = Object.freeze({
|
|
490
|
+
db: makeReader(schema, dependencies),
|
|
491
|
+
});
|
|
492
|
+
const value = yield* use(context);
|
|
493
|
+
const { revision: observedRevision } = yield* currentRevision(undefined).pipe(
|
|
494
|
+
Effect.orDie,
|
|
495
|
+
);
|
|
496
|
+
return { value, dependencies: dependencies.values(), observedRevision };
|
|
497
|
+
}),
|
|
498
|
+
);
|
|
499
|
+
const trackedMutationTransaction: LocalDatabaseService["trackedMutationTransaction"] = (
|
|
500
|
+
schema,
|
|
501
|
+
use,
|
|
502
|
+
) =>
|
|
503
|
+
sql.withTransaction(
|
|
504
|
+
Effect.gen(function* () {
|
|
505
|
+
const invalidations = makeDependencyRecorder();
|
|
506
|
+
const context = Object.freeze({
|
|
507
|
+
db: makeWriter(schema, invalidations),
|
|
508
|
+
});
|
|
509
|
+
const value = yield* use(context);
|
|
510
|
+
const { revision: committedRevision } = yield* advanceRevision(undefined).pipe(
|
|
511
|
+
Effect.orDie,
|
|
512
|
+
);
|
|
513
|
+
return { value, invalidations: invalidations.values(), committedRevision };
|
|
514
|
+
}),
|
|
515
|
+
);
|
|
516
|
+
|
|
352
517
|
return LocalDatabase.of({
|
|
518
|
+
revision: currentRevision(undefined).pipe(
|
|
519
|
+
Effect.orDie,
|
|
520
|
+
Effect.map(({ revision }) => revision),
|
|
521
|
+
),
|
|
353
522
|
queryTransaction: (schema, use) =>
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const context = Object.freeze({
|
|
357
|
-
db: makeReader(schema),
|
|
358
|
-
});
|
|
359
|
-
return yield* use(context);
|
|
360
|
-
}),
|
|
361
|
-
),
|
|
523
|
+
trackedQueryTransaction(schema, use).pipe(Effect.map(({ value }) => value)),
|
|
524
|
+
trackedQueryTransaction,
|
|
362
525
|
mutationTransaction: (schema, use) =>
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const context = Object.freeze({
|
|
366
|
-
db: makeWriter(schema),
|
|
367
|
-
});
|
|
368
|
-
return yield* use(context);
|
|
369
|
-
}),
|
|
370
|
-
),
|
|
526
|
+
trackedMutationTransaction(schema, use).pipe(Effect.map(({ value }) => value)),
|
|
527
|
+
trackedMutationTransaction,
|
|
371
528
|
});
|
|
372
529
|
}),
|
|
373
530
|
);
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DevDatabaseLockId } from "@ignotum/contracts/runtime/id";
|
|
2
|
+
import { IdGenerator } from "@ignotum/shared/id";
|
|
3
|
+
import { Effect, FileSystem, Option, Path, Predicate, Schema } from "effect";
|
|
2
4
|
|
|
3
5
|
const databaseFileNames = ["state.db", "state.db-shm", "state.db-wal", "state.db-journal"] as const;
|
|
4
6
|
const lockFileName = "lock.json";
|
|
5
7
|
|
|
6
8
|
const DevDatabaseLockRecord = Schema.Struct({
|
|
7
9
|
pid: Schema.Int,
|
|
8
|
-
token:
|
|
10
|
+
token: DevDatabaseLockId,
|
|
9
11
|
});
|
|
10
12
|
const DevDatabaseLockRecordJson = Schema.fromJsonString(DevDatabaseLockRecord);
|
|
11
13
|
type DevDatabaseLockRecord = typeof DevDatabaseLockRecord.Type;
|
|
@@ -34,9 +36,9 @@ export class DevDatabaseLockFailed extends Schema.TaggedError<DevDatabaseLockFai
|
|
|
34
36
|
},
|
|
35
37
|
) {}
|
|
36
38
|
|
|
37
|
-
export const devDatabasePath = Effect.fn("DevDatabase.path")(function* (
|
|
39
|
+
export const devDatabasePath = Effect.fn("DevDatabase.path")(function* (appDirectory: string) {
|
|
38
40
|
const path = yield* Path.Path;
|
|
39
|
-
return path.join(
|
|
41
|
+
return path.join(appDirectory, ".ignotum", "dev", "state.db");
|
|
40
42
|
});
|
|
41
43
|
|
|
42
44
|
const processIsRunning = (pid: number): boolean => {
|
|
@@ -49,17 +51,17 @@ const processIsRunning = (pid: number): boolean => {
|
|
|
49
51
|
};
|
|
50
52
|
|
|
51
53
|
export const acquireDevDatabaseLock = Effect.fn("DevDatabase.acquireLock")(function* (
|
|
52
|
-
|
|
54
|
+
appDirectory: string,
|
|
53
55
|
) {
|
|
54
|
-
const
|
|
56
|
+
const ids = yield* IdGenerator;
|
|
55
57
|
const fileSystem = yield* FileSystem.FileSystem;
|
|
56
58
|
const path = yield* Path.Path;
|
|
57
|
-
const databasePath = yield* devDatabasePath(
|
|
59
|
+
const databasePath = yield* devDatabasePath(appDirectory);
|
|
58
60
|
const directory = path.dirname(databasePath);
|
|
59
61
|
const lockPath = path.join(directory, lockFileName);
|
|
60
62
|
const record: DevDatabaseLockRecord = {
|
|
61
63
|
pid: process.pid,
|
|
62
|
-
token: yield*
|
|
64
|
+
token: yield* ids.generate(DevDatabaseLockId),
|
|
63
65
|
};
|
|
64
66
|
const encoded = yield* Schema.encodeEffect(DevDatabaseLockRecordJson)(record).pipe(
|
|
65
67
|
Effect.mapError((cause) =>
|
|
@@ -162,16 +164,14 @@ export const acquireDevDatabaseLock = Effect.fn("DevDatabase.acquireLock")(funct
|
|
|
162
164
|
yield* Effect.acquireRelease(acquire, release);
|
|
163
165
|
});
|
|
164
166
|
|
|
165
|
-
export const resetDevDatabase = Effect.fn("DevDatabase.reset")(function* (
|
|
166
|
-
projectDirectory: string,
|
|
167
|
-
) {
|
|
167
|
+
export const resetDevDatabase = Effect.fn("DevDatabase.reset")(function* (appDirectory: string) {
|
|
168
168
|
return yield* Effect.scoped(
|
|
169
169
|
Effect.gen(function* () {
|
|
170
|
-
yield* acquireDevDatabaseLock(
|
|
170
|
+
yield* acquireDevDatabaseLock(appDirectory);
|
|
171
171
|
const fileSystem = yield* FileSystem.FileSystem;
|
|
172
172
|
const path = yield* Path.Path;
|
|
173
|
-
const directory = path.join(
|
|
174
|
-
const databasePath = yield* devDatabasePath(
|
|
173
|
+
const directory = path.join(appDirectory, ".ignotum", "dev");
|
|
174
|
+
const databasePath = yield* devDatabasePath(appDirectory);
|
|
175
175
|
let removed = 0;
|
|
176
176
|
|
|
177
177
|
yield* Effect.forEach(
|