@ai-matrx/associations 0.9.33 → 0.10.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.
- package/dist/{CommentThread-CaUsmcYj.d.cts → CommentThread-B0aryGyN.d.cts} +37 -33
- package/dist/{CommentThread-CaUsmcYj.d.ts → CommentThread-B0aryGyN.d.ts} +37 -33
- package/dist/core/index.cjs +58 -51
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +12 -2
- package/dist/core/index.d.ts +12 -2
- package/dist/core/index.js +58 -51
- package/dist/core/index.js.map +1 -1
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -31
- package/dist/index.d.ts +25 -31
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.cts +2 -2
- package/dist/react/index.d.ts +2 -2
- package/dist/react/lazy/index.d.cts +1 -1
- package/dist/react/lazy/index.d.ts +1 -1
- package/package.json +5 -5
|
@@ -276,6 +276,22 @@ interface DemandedRpcSignatures {
|
|
|
276
276
|
title: string;
|
|
277
277
|
}[];
|
|
278
278
|
};
|
|
279
|
+
entity_row_create: {
|
|
280
|
+
Args: {
|
|
281
|
+
p_organization_id: string;
|
|
282
|
+
p_title: string;
|
|
283
|
+
p_token: string;
|
|
284
|
+
};
|
|
285
|
+
Returns: unknown;
|
|
286
|
+
};
|
|
287
|
+
entity_row_rename: {
|
|
288
|
+
Args: {
|
|
289
|
+
p_id: string;
|
|
290
|
+
p_title: string;
|
|
291
|
+
p_token: string;
|
|
292
|
+
};
|
|
293
|
+
Returns: unknown;
|
|
294
|
+
};
|
|
279
295
|
cmt_list: {
|
|
280
296
|
Args: {
|
|
281
297
|
p_entity_id: string;
|
|
@@ -364,27 +380,6 @@ interface PgError {
|
|
|
364
380
|
hint?: string | null;
|
|
365
381
|
}
|
|
366
382
|
|
|
367
|
-
/**
|
|
368
|
-
* The minimal table surface entity-row create/rename needs (a structural
|
|
369
|
-
* subset of supabase-js's query builder). Registry-convention writes go
|
|
370
|
-
* DIRECT to the backing table (they are RLS-gated feature rows, not
|
|
371
|
-
* association edges — there is no RPC for them by design).
|
|
372
|
-
*/
|
|
373
|
-
interface AssociationsTableQuery {
|
|
374
|
-
insert(row: Record<string, unknown>): {
|
|
375
|
-
select(columns: string): {
|
|
376
|
-
single(): PromiseLike<{
|
|
377
|
-
data: unknown;
|
|
378
|
-
error: PgError | null;
|
|
379
|
-
}>;
|
|
380
|
-
};
|
|
381
|
-
};
|
|
382
|
-
update(patch: Record<string, unknown>): {
|
|
383
|
-
eq(column: string, value: string): PromiseLike<{
|
|
384
|
-
error: PgError | null;
|
|
385
|
-
}>;
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
383
|
/**
|
|
389
384
|
* The data seam — a structural subset of `SupabaseClient<Database>`: any
|
|
390
385
|
* client that can answer the demanded RPC families (README § The demanded
|
|
@@ -392,22 +387,21 @@ interface AssociationsTableQuery {
|
|
|
392
387
|
* one. `PromiseLike` because supabase-js returns a thenable builder, not a
|
|
393
388
|
* `Promise`.
|
|
394
389
|
*
|
|
395
|
-
*
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
390
|
+
* 🚨 RPC AND NOTHING ELSE (DOORS-ONLY-4, 2026-09-21). This port used to carry
|
|
391
|
+
* OPTIONAL `from`/`schema` table surfaces, used by exactly one service —
|
|
392
|
+
* entity-row create/rename, which wrote the registered entity's backing table
|
|
393
|
+
* directly. The doors-only ruling (`platform` and `iam` are not client-writable;
|
|
394
|
+
* every write goes through a named SECURITY DEFINER door) made that path answer
|
|
395
|
+
* `42501` on every closed table, so it now calls `entity_row_create` /
|
|
396
|
+
* `entity_row_rename` like every other operation. With its one reader gone the
|
|
397
|
+
* two optional members were a port nobody reads — removed rather than left as a
|
|
398
|
+
* seam a future write could quietly reach for.
|
|
401
399
|
*/
|
|
402
400
|
interface AssociationsDataSource {
|
|
403
401
|
rpc(fn: DemandedRpcName, args: Record<string, unknown>): PromiseLike<{
|
|
404
402
|
data: unknown;
|
|
405
403
|
error: PgError | null;
|
|
406
404
|
}>;
|
|
407
|
-
from?(table: string): AssociationsTableQuery;
|
|
408
|
-
schema?(name: string): {
|
|
409
|
-
from(table: string): AssociationsTableQuery;
|
|
410
|
-
};
|
|
411
405
|
}
|
|
412
406
|
/**
|
|
413
407
|
* The identity seam — today a store-singleton read (`requireUserId`) plus the
|
|
@@ -1100,9 +1094,19 @@ type EntityRowResult = {
|
|
|
1100
1094
|
interface CreateEntityRowArgs {
|
|
1101
1095
|
/** The new row's human name — written to the registry `titleColumn`. */
|
|
1102
1096
|
title: string;
|
|
1103
|
-
/**
|
|
1097
|
+
/**
|
|
1098
|
+
* The organization the new row belongs to. REQUIRED by
|
|
1099
|
+
* `public.entity_row_create`: every creatable token's table carries
|
|
1100
|
+
* `organization_id`, and the door refuses a row with nobody owning it.
|
|
1101
|
+
*/
|
|
1104
1102
|
orgId?: string | null;
|
|
1105
|
-
/**
|
|
1103
|
+
/**
|
|
1104
|
+
* 🚨 NO LONGER WRITABLE THROUGH THIS SERVICE (DOORS-ONLY-4). The door writes the
|
|
1105
|
+
* name, the owner and the organization and nothing else, so a non-empty value here
|
|
1106
|
+
* is REFUSED with the columns named rather than dropped on the floor. A token whose
|
|
1107
|
+
* table needs more than a name is created by its own feature's create service; this
|
|
1108
|
+
* generic path then only renames it.
|
|
1109
|
+
*/
|
|
1106
1110
|
extraColumns?: Record<string, unknown>;
|
|
1107
1111
|
}
|
|
1108
1112
|
interface EntityRowsServiceApi {
|
|
@@ -276,6 +276,22 @@ interface DemandedRpcSignatures {
|
|
|
276
276
|
title: string;
|
|
277
277
|
}[];
|
|
278
278
|
};
|
|
279
|
+
entity_row_create: {
|
|
280
|
+
Args: {
|
|
281
|
+
p_organization_id: string;
|
|
282
|
+
p_title: string;
|
|
283
|
+
p_token: string;
|
|
284
|
+
};
|
|
285
|
+
Returns: unknown;
|
|
286
|
+
};
|
|
287
|
+
entity_row_rename: {
|
|
288
|
+
Args: {
|
|
289
|
+
p_id: string;
|
|
290
|
+
p_title: string;
|
|
291
|
+
p_token: string;
|
|
292
|
+
};
|
|
293
|
+
Returns: unknown;
|
|
294
|
+
};
|
|
279
295
|
cmt_list: {
|
|
280
296
|
Args: {
|
|
281
297
|
p_entity_id: string;
|
|
@@ -364,27 +380,6 @@ interface PgError {
|
|
|
364
380
|
hint?: string | null;
|
|
365
381
|
}
|
|
366
382
|
|
|
367
|
-
/**
|
|
368
|
-
* The minimal table surface entity-row create/rename needs (a structural
|
|
369
|
-
* subset of supabase-js's query builder). Registry-convention writes go
|
|
370
|
-
* DIRECT to the backing table (they are RLS-gated feature rows, not
|
|
371
|
-
* association edges — there is no RPC for them by design).
|
|
372
|
-
*/
|
|
373
|
-
interface AssociationsTableQuery {
|
|
374
|
-
insert(row: Record<string, unknown>): {
|
|
375
|
-
select(columns: string): {
|
|
376
|
-
single(): PromiseLike<{
|
|
377
|
-
data: unknown;
|
|
378
|
-
error: PgError | null;
|
|
379
|
-
}>;
|
|
380
|
-
};
|
|
381
|
-
};
|
|
382
|
-
update(patch: Record<string, unknown>): {
|
|
383
|
-
eq(column: string, value: string): PromiseLike<{
|
|
384
|
-
error: PgError | null;
|
|
385
|
-
}>;
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
383
|
/**
|
|
389
384
|
* The data seam — a structural subset of `SupabaseClient<Database>`: any
|
|
390
385
|
* client that can answer the demanded RPC families (README § The demanded
|
|
@@ -392,22 +387,21 @@ interface AssociationsTableQuery {
|
|
|
392
387
|
* one. `PromiseLike` because supabase-js returns a thenable builder, not a
|
|
393
388
|
* `Promise`.
|
|
394
389
|
*
|
|
395
|
-
*
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
390
|
+
* 🚨 RPC AND NOTHING ELSE (DOORS-ONLY-4, 2026-09-21). This port used to carry
|
|
391
|
+
* OPTIONAL `from`/`schema` table surfaces, used by exactly one service —
|
|
392
|
+
* entity-row create/rename, which wrote the registered entity's backing table
|
|
393
|
+
* directly. The doors-only ruling (`platform` and `iam` are not client-writable;
|
|
394
|
+
* every write goes through a named SECURITY DEFINER door) made that path answer
|
|
395
|
+
* `42501` on every closed table, so it now calls `entity_row_create` /
|
|
396
|
+
* `entity_row_rename` like every other operation. With its one reader gone the
|
|
397
|
+
* two optional members were a port nobody reads — removed rather than left as a
|
|
398
|
+
* seam a future write could quietly reach for.
|
|
401
399
|
*/
|
|
402
400
|
interface AssociationsDataSource {
|
|
403
401
|
rpc(fn: DemandedRpcName, args: Record<string, unknown>): PromiseLike<{
|
|
404
402
|
data: unknown;
|
|
405
403
|
error: PgError | null;
|
|
406
404
|
}>;
|
|
407
|
-
from?(table: string): AssociationsTableQuery;
|
|
408
|
-
schema?(name: string): {
|
|
409
|
-
from(table: string): AssociationsTableQuery;
|
|
410
|
-
};
|
|
411
405
|
}
|
|
412
406
|
/**
|
|
413
407
|
* The identity seam — today a store-singleton read (`requireUserId`) plus the
|
|
@@ -1100,9 +1094,19 @@ type EntityRowResult = {
|
|
|
1100
1094
|
interface CreateEntityRowArgs {
|
|
1101
1095
|
/** The new row's human name — written to the registry `titleColumn`. */
|
|
1102
1096
|
title: string;
|
|
1103
|
-
/**
|
|
1097
|
+
/**
|
|
1098
|
+
* The organization the new row belongs to. REQUIRED by
|
|
1099
|
+
* `public.entity_row_create`: every creatable token's table carries
|
|
1100
|
+
* `organization_id`, and the door refuses a row with nobody owning it.
|
|
1101
|
+
*/
|
|
1104
1102
|
orgId?: string | null;
|
|
1105
|
-
/**
|
|
1103
|
+
/**
|
|
1104
|
+
* 🚨 NO LONGER WRITABLE THROUGH THIS SERVICE (DOORS-ONLY-4). The door writes the
|
|
1105
|
+
* name, the owner and the organization and nothing else, so a non-empty value here
|
|
1106
|
+
* is REFUSED with the columns named rather than dropped on the floor. A token whose
|
|
1107
|
+
* table needs more than a name is created by its own feature's create service; this
|
|
1108
|
+
* generic path then only renames it.
|
|
1109
|
+
*/
|
|
1106
1110
|
extraColumns?: Record<string, unknown>;
|
|
1107
1111
|
}
|
|
1108
1112
|
interface EntityRowsServiceApi {
|
package/dist/core/index.cjs
CHANGED
|
@@ -3195,31 +3195,7 @@ function createTitlesService(deps, candidates) {
|
|
|
3195
3195
|
|
|
3196
3196
|
// src/core/entityRows.ts
|
|
3197
3197
|
function createEntityRowsService(deps, titles) {
|
|
3198
|
-
const { dataSource,
|
|
3199
|
-
function tableFor(token, schema, table) {
|
|
3200
|
-
if (schema && schema !== "public") {
|
|
3201
|
-
if (!dataSource.schema) {
|
|
3202
|
-
const error = `dataSource has no schema() capability \u2014 entity-row writes for "${token}" (${schema}.${table}) need a table-capable dataSource (a real supabase-js client)`;
|
|
3203
|
-
errorSink({
|
|
3204
|
-
code: "datasource_missing_table_capability",
|
|
3205
|
-
message: `[entityRows] ${error}`,
|
|
3206
|
-
context: { token, schema, table }
|
|
3207
|
-
});
|
|
3208
|
-
return { ok: false, error };
|
|
3209
|
-
}
|
|
3210
|
-
return { ok: true, query: dataSource.schema(schema).from(table) };
|
|
3211
|
-
}
|
|
3212
|
-
if (!dataSource.from) {
|
|
3213
|
-
const error = `dataSource has no from() capability \u2014 entity-row writes for "${token}" need a table-capable dataSource (a real supabase-js client)`;
|
|
3214
|
-
errorSink({
|
|
3215
|
-
code: "datasource_missing_table_capability",
|
|
3216
|
-
message: `[entityRows] ${error}`,
|
|
3217
|
-
context: { token, schema, table }
|
|
3218
|
-
});
|
|
3219
|
-
return { ok: false, error };
|
|
3220
|
-
}
|
|
3221
|
-
return { ok: true, query: dataSource.from(table) };
|
|
3222
|
-
}
|
|
3198
|
+
const { dataSource, registry, errorSink } = deps;
|
|
3223
3199
|
function requireTitleColumn(token, op) {
|
|
3224
3200
|
const info = registry.getEntityInfo(token);
|
|
3225
3201
|
if (!info.titleColumn) {
|
|
@@ -3231,43 +3207,68 @@ function createEntityRowsService(deps, titles) {
|
|
|
3231
3207
|
});
|
|
3232
3208
|
return { ok: false, error };
|
|
3233
3209
|
}
|
|
3234
|
-
return { ok: true
|
|
3210
|
+
return { ok: true };
|
|
3235
3211
|
}
|
|
3236
3212
|
return {
|
|
3237
3213
|
/**
|
|
3238
|
-
*
|
|
3239
|
-
*
|
|
3240
|
-
*
|
|
3241
|
-
*
|
|
3214
|
+
* Create one row of `token`'s backing table named `title`, through
|
|
3215
|
+
* `public.entity_row_create`. The door stamps `created_by` from `auth.uid()`,
|
|
3216
|
+
* checks the caller can act in the organization, and refuses machinery tokens and
|
|
3217
|
+
* tables with no organization by name. Returns the new row's id + title. The
|
|
3218
|
+
* caller wires associations afterwards — this layer never writes edges.
|
|
3242
3219
|
*/
|
|
3243
3220
|
async createEntityRow(token, args) {
|
|
3244
3221
|
const gate = requireTitleColumn(token, "creating rows");
|
|
3245
3222
|
if (!gate.ok) return gate;
|
|
3246
|
-
const info = registry.getEntityInfo(token);
|
|
3247
3223
|
const title = args.title.trim();
|
|
3248
3224
|
if (!title) return { ok: false, error: "Name is required" };
|
|
3225
|
+
const extras = Object.keys(args.extraColumns ?? {});
|
|
3226
|
+
if (extras.length > 0) {
|
|
3227
|
+
const error = `A new ${registry.getEntityInfo(token).label} cannot be created here with ${extras.join(", ")}: the shared create door writes the name, the owner and the organization and nothing else. Create it with this feature's own create service, then attach it.`;
|
|
3228
|
+
errorSink({
|
|
3229
|
+
code: "entity_row_extra_columns_unsupported",
|
|
3230
|
+
message: `[createEntityRow] extraColumns are not writable through entity_row_create for "${token}": ${extras.join(", ")}`,
|
|
3231
|
+
context: { token, extras }
|
|
3232
|
+
});
|
|
3233
|
+
return { ok: false, error };
|
|
3234
|
+
}
|
|
3235
|
+
if (!args.orgId) {
|
|
3236
|
+
const error = `Name the organization this ${registry.getEntityInfo(token).label} belongs to.`;
|
|
3237
|
+
errorSink({
|
|
3238
|
+
code: "entity_row_missing_organization",
|
|
3239
|
+
message: `[createEntityRow] no organization supplied for "${token}"; entity_row_create requires one`,
|
|
3240
|
+
context: { token }
|
|
3241
|
+
});
|
|
3242
|
+
return { ok: false, error };
|
|
3243
|
+
}
|
|
3249
3244
|
try {
|
|
3250
|
-
const
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
...info.ownerColumn ? { [info.ownerColumn]: userId } : {},
|
|
3256
|
-
...info.orgColumn && args.orgId ? { [info.orgColumn]: args.orgId } : {},
|
|
3257
|
-
...args.extraColumns ?? {}
|
|
3258
|
-
};
|
|
3259
|
-
const { data, error } = await table.query.insert(row).select("id").single();
|
|
3245
|
+
const { data, error } = await dataSource.rpc("entity_row_create", {
|
|
3246
|
+
p_token: token,
|
|
3247
|
+
p_title: title,
|
|
3248
|
+
p_organization_id: args.orgId
|
|
3249
|
+
});
|
|
3260
3250
|
if (error) {
|
|
3261
3251
|
errorSink({
|
|
3262
3252
|
code: "entity_row_insert_failed",
|
|
3263
|
-
message: `[createEntityRow]
|
|
3253
|
+
message: `[createEntityRow] entity_row_create failed for "${token}": ${error.message}`,
|
|
3264
3254
|
context: { token, detail: error }
|
|
3265
3255
|
});
|
|
3266
3256
|
return { ok: false, error: error.message };
|
|
3267
3257
|
}
|
|
3268
|
-
const
|
|
3269
|
-
|
|
3270
|
-
|
|
3258
|
+
const row = data;
|
|
3259
|
+
if (!row || typeof row.id !== "string" || !row.id) {
|
|
3260
|
+
const msg = "The create door returned no record.";
|
|
3261
|
+
errorSink({
|
|
3262
|
+
code: "entity_row_insert_failed",
|
|
3263
|
+
message: `[createEntityRow] entity_row_create returned no id for "${token}"`,
|
|
3264
|
+
context: { token, detail: data }
|
|
3265
|
+
});
|
|
3266
|
+
return { ok: false, error: msg };
|
|
3267
|
+
}
|
|
3268
|
+
const id = row.id;
|
|
3269
|
+
const named = typeof row.title === "string" && row.title ? row.title : title;
|
|
3270
|
+
titles.prime(token, id, named);
|
|
3271
|
+
return { ok: true, data: { id, title: named } };
|
|
3271
3272
|
} catch (e) {
|
|
3272
3273
|
const msg = e instanceof Error ? e.message : "Failed to create";
|
|
3273
3274
|
errorSink({
|
|
@@ -3278,21 +3279,27 @@ function createEntityRowsService(deps, titles) {
|
|
|
3278
3279
|
return { ok: false, error: msg };
|
|
3279
3280
|
}
|
|
3280
3281
|
},
|
|
3281
|
-
/**
|
|
3282
|
+
/**
|
|
3283
|
+
* Rename one row of `token`'s table through `public.entity_row_rename`. The door
|
|
3284
|
+
* decides on the one ladder — the row's author, `editor` on the row, or a platform
|
|
3285
|
+
* admin — and answers "there is no such thing here" rather than telling a caller
|
|
3286
|
+
* that somebody else's row exists.
|
|
3287
|
+
*/
|
|
3282
3288
|
async renameEntityRow(token, id, title) {
|
|
3283
3289
|
const gate = requireTitleColumn(token, "renaming rows");
|
|
3284
3290
|
if (!gate.ok) return gate;
|
|
3285
|
-
const info = registry.getEntityInfo(token);
|
|
3286
3291
|
const trimmed = title.trim();
|
|
3287
3292
|
if (!trimmed) return { ok: false, error: "Name is required" };
|
|
3288
3293
|
try {
|
|
3289
|
-
const
|
|
3290
|
-
|
|
3291
|
-
|
|
3294
|
+
const { error } = await dataSource.rpc("entity_row_rename", {
|
|
3295
|
+
p_token: token,
|
|
3296
|
+
p_id: id,
|
|
3297
|
+
p_title: trimmed
|
|
3298
|
+
});
|
|
3292
3299
|
if (error) {
|
|
3293
3300
|
errorSink({
|
|
3294
3301
|
code: "entity_row_update_failed",
|
|
3295
|
-
message: `[renameEntityRow]
|
|
3302
|
+
message: `[renameEntityRow] entity_row_rename failed for "${token}": ${error.message}`,
|
|
3296
3303
|
context: { token, id, detail: error }
|
|
3297
3304
|
});
|
|
3298
3305
|
return { ok: false, error: error.message };
|