@kubun/store-graph 0.17.0 → 0.17.1

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/lib/api.d.ts CHANGED
@@ -1,8 +1,36 @@
1
1
  import type { Adapter } from '@kubun/db-adapter';
2
2
  import { DocumentID, DocumentModelID } from '@kubun/id';
3
- import type { AggregateDocumentsParams, AggregateValues, DocumentModel, DocumentNode, GroupedAggregateDocumentsParams, GroupedAggregateValue } from '@kubun/protocol';
3
+ import type { AggregateDocumentsParams, AggregateValues, DocumentFieldsMeta, DocumentModel, DocumentModelSchema, DocumentNode, GroupedAggregateDocumentsParams, GroupedAggregateValue } from '@kubun/protocol';
4
4
  import type { Kysely } from 'kysely';
5
- import type { AccessLevel, Catalog, ClusterDefinitionRow, ClusterModel, CreateDocumentParams, CreateGraphParams, GraphModel, GraphModelWithRecord, GraphTables, InsertCatalog, InsertClusterModel, InsertMutationLogEntry, ListDocumentsParams, MutationLogEntry, QueryDocumentsParams, QueryDocumentsResult, SaveDocumentParams, SearchDocumentResult, SearchDocumentsParams } from './tables.js';
5
+ import type { AccessLevel, Catalog, ClusterDefinitionRow, ClusterModel, CreateDocumentParams, CreateGraphParams, DocumentModelRow, GraphModel, GraphModelWithRecord, GraphTables, InsertCatalog, InsertClusterModel, InsertMutationLogEntry, ListDocumentsParams, MutationLogEntry, QueryDocumentsParams, QueryDocumentsResult, SaveDocumentParams, SearchDocumentResult, SearchDocumentsParams } from './tables.js';
6
+ export declare function documentModelFromTable(row: DocumentModelRow): DocumentModel;
7
+ export declare function documentModelToTable(adapter: Adapter, id: string, model: DocumentModel): {
8
+ id: string;
9
+ version: "1.0";
10
+ name: string;
11
+ behavior: "unique" | "default" | "interface";
12
+ unique_fields: string[] | null;
13
+ interfaces: Array<string>;
14
+ schema: DocumentModelSchema;
15
+ fields_meta: DocumentFieldsMeta;
16
+ provider_merge_policy: {
17
+ group: Array<string>;
18
+ } | null;
19
+ };
20
+ /**
21
+ * Reads a document's `field_values` cell on the given transaction and
22
+ * delegates the merge-policy liveness comparison to `@kubun/mutation`'s pure
23
+ * `winnerStateDigestOf`. Returns the winning live `$setFetch` digest, the
24
+ * constant deleted-state digest when the delete side wins, or `null` when the
25
+ * document (or its fetch-order registers) is absent.
26
+ *
27
+ * Because graph and connector tables share one DB connection (the KubunDB
28
+ * coordinator), a caller with its own transaction over a wider `DB` type
29
+ * passes it here cast to `Kysely<GraphTables>` (the same pattern
30
+ * `addDocumentModel` above already relies on) so the read joins that
31
+ * transaction rather than opening a second connection.
32
+ */
33
+ export declare function readWinnerStateDigest(tx: Kysely<GraphTables>, documentID: string): Promise<string | null>;
6
34
  /**
7
35
  * Persisted access rule shape as stored in `user_model_access_defaults`.
8
36
  */
package/lib/api.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { DocumentID, DocumentModelID } from '@kubun/id';
2
+ import { winnerStateDigestOf } from '@kubun/mutation';
2
3
  import { serializeCursor } from './cursor.js';
3
4
  import { isMissingTableError, ModelNotDeployedError } from './errors.js';
4
5
  import { aggregateFieldName, applyAggregateSelections, applyDocumentFilter, applyDocumentOrderBy, applyGroupBySelections, applyPagination, groupKeyAlias, knownFieldColumn } from './query-builder.js';
@@ -254,21 +255,22 @@ function compareGroups(a, b, orderBy) {
254
255
  }
255
256
  return 0;
256
257
  }
257
- function documentModelFromTable(row) {
258
+ export function documentModelFromTable(row) {
258
259
  const model = {
259
260
  version: row.version,
260
261
  name: row.name,
261
262
  behavior: row.behavior,
262
263
  interfaces: row.interfaces,
263
264
  schema: row.schema,
264
- fieldsMeta: row.fields_meta
265
+ fieldsMeta: row.fields_meta,
266
+ providerMergePolicy: row.provider_merge_policy ?? undefined
265
267
  };
266
268
  if (model.behavior === 'unique') {
267
269
  model.uniqueFields = row.unique_fields;
268
270
  }
269
271
  return model;
270
272
  }
271
- function documentModelToTable(adapter, id, model) {
273
+ export function documentModelToTable(adapter, id, model) {
272
274
  return {
273
275
  id,
274
276
  version: model.version,
@@ -277,7 +279,8 @@ function documentModelToTable(adapter, id, model) {
277
279
  unique_fields: model.behavior === 'unique' ? adapter.encodeJSON(model.uniqueFields) : null,
278
280
  interfaces: adapter.encodeJSON(model.interfaces),
279
281
  schema: adapter.encodeJSON(model.schema),
280
- fields_meta: adapter.encodeJSON(model.fieldsMeta)
282
+ fields_meta: adapter.encodeJSON(model.fieldsMeta),
283
+ provider_merge_policy: model.providerMergePolicy ? adapter.encodeJSON(model.providerMergePolicy) : null
281
284
  };
282
285
  }
283
286
  function documentNodeFromTable(adapter, doc) {
@@ -322,6 +325,29 @@ async function addDocumentModel(transaction, adapter, params) {
322
325
  document_model_id: id
323
326
  }).onConflict((oc)=>oc.doNothing()).execute();
324
327
  }
328
+ // --- Cross-plan export: tx-scoped merge-policy winner read ---
329
+ /**
330
+ * Reads a document's `field_values` cell on the given transaction and
331
+ * delegates the merge-policy liveness comparison to `@kubun/mutation`'s pure
332
+ * `winnerStateDigestOf`. Returns the winning live `$setFetch` digest, the
333
+ * constant deleted-state digest when the delete side wins, or `null` when the
334
+ * document (or its fetch-order registers) is absent.
335
+ *
336
+ * Because graph and connector tables share one DB connection (the KubunDB
337
+ * coordinator), a caller with its own transaction over a wider `DB` type
338
+ * passes it here cast to `Kysely<GraphTables>` (the same pattern
339
+ * `addDocumentModel` above already relies on) so the read joins that
340
+ * transaction rather than opening a second connection.
341
+ */ export async function readWinnerStateDigest(tx, documentID) {
342
+ const modelID = DocumentID.fromString(documentID).model.toString();
343
+ const row = await tx.selectFrom(`k_${modelID}`).select('field_values').where('id', '=', documentID).executeTakeFirst();
344
+ if (row?.field_values == null) {
345
+ return null;
346
+ }
347
+ // ParseJSONResultsPlugin may have already parsed the value.
348
+ const fieldValues = typeof row.field_values === 'string' ? JSON.parse(row.field_values) : row.field_values;
349
+ return winnerStateDigestOf(fieldValues);
350
+ }
325
351
  // --- Factory function ---
326
352
  export function createGraphStore(db, adapter) {
327
353
  const coerce = adapter.coerceFilterValue.bind(adapter);
@@ -443,7 +469,9 @@ export function createGraphStore(db, adapter) {
443
469
  id,
444
470
  owner: params.owner,
445
471
  model,
446
- data: adapter.encodeJSON(params.data),
472
+ // Null data (a first-seen tombstone establishing a dead row) persists
473
+ // as SQL null, not the JSON string "null" — mirrors `saveDocument`.
474
+ data: params.data ? adapter.encodeJSON(params.data) : null,
447
475
  unique: adapter.encodeBinary(params.unique)
448
476
  }).returningAll().executeTakeFirstOrThrow(()=>new Error(`createDocument failed to return row for ${id}`));
449
477
  if (params.data) {
package/lib/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { StoreProvider } from '@kubun/db';
2
2
  import type { GraphStoreAPI } from './api.js';
3
3
  export type { GraphStoreAPI, StoredAccessDefaultRow, StoredAccessRule } from './api.js';
4
- export { createGraphStore } from './api.js';
4
+ export { createGraphStore, readWinnerStateDigest } from './api.js';
5
5
  export { graphStoreDefinition } from './definition.js';
6
6
  export { ModelNotDeployedError } from './errors.js';
7
7
  export { resolveSearchableFields, SearchNotEnabledError } from './search.js';
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { createGraphStore } from './api.js';
1
+ export { createGraphStore, readWinnerStateDigest } from './api.js';
2
2
  export { graphStoreDefinition } from './definition.js';
3
3
  export { ModelNotDeployedError } from './errors.js';
4
4
  export { resolveSearchableFields, SearchNotEnabledError } from './search.js';
package/lib/migrations.js CHANGED
@@ -16,7 +16,7 @@
16
16
  const init = {
17
17
  async up (db) {
18
18
  // Document models
19
- await db.schema.createTable('kubun_graph_document_models').ifNotExists().addColumn('id', t.text, (col)=>col.notNull().primaryKey()).addColumn('version', t.text, (col)=>col.notNull()).addColumn('name', t.text, (col)=>col.notNull()).addColumn('behavior', t.text, (col)=>col.notNull()).addColumn('unique_fields', t.json).addColumn('interfaces', t.json, (col)=>col.notNull()).addColumn('schema', t.json, (col)=>col.notNull()).addColumn('fields_meta', t.json, (col)=>col.notNull()).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).execute();
19
+ await db.schema.createTable('kubun_graph_document_models').ifNotExists().addColumn('id', t.text, (col)=>col.notNull().primaryKey()).addColumn('version', t.text, (col)=>col.notNull()).addColumn('name', t.text, (col)=>col.notNull()).addColumn('behavior', t.text, (col)=>col.notNull()).addColumn('unique_fields', t.json).addColumn('interfaces', t.json, (col)=>col.notNull()).addColumn('schema', t.json, (col)=>col.notNull()).addColumn('fields_meta', t.json, (col)=>col.notNull()).addColumn('provider_merge_policy', t.json).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).execute();
20
20
  // Document model interfaces
21
21
  await db.schema.createTable('kubun_graph_document_model_interfaces').ifNotExists().addColumn('interface_id', t.text, (col)=>col.notNull().references('kubun_graph_document_models.id')).addColumn('implementation_id', t.text, (col)=>col.notNull().references('kubun_graph_document_models.id')).addUniqueConstraint('kubun_graph_document_model_interfaces_pkey', [
22
22
  'interface_id',
package/lib/tables.d.ts CHANGED
@@ -27,6 +27,9 @@ export type DocumentModelTable = {
27
27
  interfaces: JSONValueColumn<Array<string>>;
28
28
  schema: JSONValueColumn<DocumentModelSchema>;
29
29
  fields_meta: JSONValueColumn<DocumentFieldsMeta>;
30
+ provider_merge_policy: JSONValueColumn<{
31
+ group: Array<string>;
32
+ }> | null;
30
33
  created_at: CreatedAtColumn;
31
34
  updated_at: UpdatedAtColumn;
32
35
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubun/store-graph",
3
- "version": "0.17.0",
3
+ "version": "0.17.1",
4
4
  "keywords": [],
5
5
  "license": "see LICENSE.md",
6
6
  "sideEffects": false,
@@ -18,13 +18,14 @@
18
18
  "@kubun/db": "^0.17.0",
19
19
  "@kubun/db-adapter": "^0.17.0",
20
20
  "@kubun/id": "^0.17.0",
21
- "@kubun/protocol": "^0.17.0",
21
+ "@kubun/mutation": "^0.17.1",
22
+ "@kubun/protocol": "^0.17.1",
22
23
  "@sozai/codec": "^0.4.0",
23
- "kysely": "^0.29.5"
24
+ "kysely": "^0.29.6"
24
25
  },
25
26
  "devDependencies": {
26
27
  "@kubun/db-better-sqlite": "^0.17.0",
27
- "@kubun/db-postgres": "^0.17.0",
28
+ "@kubun/db-postgres": "^0.17.1",
28
29
  "@testcontainers/postgresql": "^12.1.0"
29
30
  },
30
31
  "publishConfig": {