@objectstack/objectql 9.9.1 → 9.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/index.d.mts +49 -1
- package/dist/index.d.ts +49 -1
- package/dist/index.js +71 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +71 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
package/dist/index.d.mts
CHANGED
|
@@ -141,6 +141,22 @@ interface SchemaRegistryOptions {
|
|
|
141
141
|
* timestamps; the `*_by` lookups are filled by the runtime when an
|
|
142
142
|
* authenticated session is present (NULL otherwise — e.g. seeded
|
|
143
143
|
* rows).
|
|
144
|
+
* - `owner_id` — canonical, *reassignable* record owner (lookup to
|
|
145
|
+
* `sys_user`). Auto-provisioned by DEFAULT on user-authored business
|
|
146
|
+
* objects so ownership is correct-by-default for AI/human authors who
|
|
147
|
+
* would otherwise forget to declare it (or reinvent a custom `owner`
|
|
148
|
+
* lookup the platform can't see). Once present, the existing machinery
|
|
149
|
+
* engages automatically: SecurityPlugin auto-stamps it to the acting
|
|
150
|
+
* user on insert (step 3.5), owner-scoped RLS / "My" views / owner
|
|
151
|
+
* reports key off it, and the first-admin bootstrap hands seeded rows
|
|
152
|
+
* (owner_id NULL) to the promoted admin. Unlike `created_by` it is
|
|
153
|
+
* editable (`readonly: false`) — ownership transfers; provenance does
|
|
154
|
+
* not. Excluded for `managedBy` / `sys_*` tables and any object that
|
|
155
|
+
* opts out via `ownership: 'org' | 'none'` (Dataverse-style: a
|
|
156
|
+
* catalog/junction table that has no per-record owner). Forgetting the
|
|
157
|
+
* opt-out is harmless (a spare nullable column), whereas forgetting to
|
|
158
|
+
* ADD ownership — the failure mode we are eliminating — silently breaks
|
|
159
|
+
* every owner-keyed feature.
|
|
144
160
|
*/
|
|
145
161
|
declare function applySystemFields(schema: ServiceObject, opts: {
|
|
146
162
|
multiTenant: boolean;
|
|
@@ -612,7 +628,7 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
|
|
|
612
628
|
} | {
|
|
613
629
|
language: "js";
|
|
614
630
|
source: string;
|
|
615
|
-
capabilities: ("log" | "api.read" | "api.write" | "crypto.uuid" | "crypto.hash")[];
|
|
631
|
+
capabilities: ("log" | "api.read" | "api.write" | "api.transaction" | "crypto.uuid" | "crypto.hash")[];
|
|
616
632
|
timeoutMs?: number | undefined;
|
|
617
633
|
memoryMb?: number | undefined;
|
|
618
634
|
} | undefined;
|
|
@@ -2664,6 +2680,38 @@ declare class ScopedContext {
|
|
|
2664
2680
|
* does not support transactions.
|
|
2665
2681
|
*/
|
|
2666
2682
|
transaction(callback: (trxCtx: ScopedContext) => Promise<any>): Promise<any>;
|
|
2683
|
+
/**
|
|
2684
|
+
* Resolve the default driver, if it exposes transaction primitives.
|
|
2685
|
+
* Shared by {@link transaction} and the discrete begin/commit/rollback trio.
|
|
2686
|
+
*/
|
|
2687
|
+
private txDriver;
|
|
2688
|
+
/**
|
|
2689
|
+
* Discrete transaction primitives — `begin` / `commit` / `rollback` as three
|
|
2690
|
+
* separate calls, in contrast to {@link transaction}'s single-callback form.
|
|
2691
|
+
*
|
|
2692
|
+
* This trio exists for callers that cannot keep a JS closure on the stack for
|
|
2693
|
+
* the lifetime of the transaction — chiefly the sandbox runner, where the
|
|
2694
|
+
* hook/action body's `ctx.api.transaction(fn)` is driven across many host
|
|
2695
|
+
* event-loop turns via deferred promises. Across those `setImmediate`
|
|
2696
|
+
* boundaries the engine's ambient `txStore` (AsyncLocalStorage) does NOT
|
|
2697
|
+
* survive, so the transaction handle is threaded **explicitly**: `begin`
|
|
2698
|
+
* returns a child ScopedContext carrying `transaction: trx` in its execution
|
|
2699
|
+
* context, and `resolveTx` honors that explicit handle ahead of the ambient
|
|
2700
|
+
* store. Every `object(...)` op on the returned context therefore reuses the
|
|
2701
|
+
* one connection without relying on ALS.
|
|
2702
|
+
*
|
|
2703
|
+
* Returns `null` when the driver has no transaction support — the caller then
|
|
2704
|
+
* runs non-transactionally against `this` (same graceful degrade as
|
|
2705
|
+
* {@link transaction}).
|
|
2706
|
+
*/
|
|
2707
|
+
beginTransaction(): Promise<{
|
|
2708
|
+
ctx: ScopedContext;
|
|
2709
|
+
handle: unknown;
|
|
2710
|
+
} | null>;
|
|
2711
|
+
/** Commit a handle obtained from {@link beginTransaction}. */
|
|
2712
|
+
commitTransaction(handle: unknown): Promise<void>;
|
|
2713
|
+
/** Roll back a handle obtained from {@link beginTransaction}. */
|
|
2714
|
+
rollbackTransaction(handle: unknown): Promise<void>;
|
|
2667
2715
|
get userId(): string | undefined;
|
|
2668
2716
|
get tenantId(): string | undefined;
|
|
2669
2717
|
/** Alias for tenantId — matches ObjectQLContext.spaceId convention */
|
package/dist/index.d.ts
CHANGED
|
@@ -141,6 +141,22 @@ interface SchemaRegistryOptions {
|
|
|
141
141
|
* timestamps; the `*_by` lookups are filled by the runtime when an
|
|
142
142
|
* authenticated session is present (NULL otherwise — e.g. seeded
|
|
143
143
|
* rows).
|
|
144
|
+
* - `owner_id` — canonical, *reassignable* record owner (lookup to
|
|
145
|
+
* `sys_user`). Auto-provisioned by DEFAULT on user-authored business
|
|
146
|
+
* objects so ownership is correct-by-default for AI/human authors who
|
|
147
|
+
* would otherwise forget to declare it (or reinvent a custom `owner`
|
|
148
|
+
* lookup the platform can't see). Once present, the existing machinery
|
|
149
|
+
* engages automatically: SecurityPlugin auto-stamps it to the acting
|
|
150
|
+
* user on insert (step 3.5), owner-scoped RLS / "My" views / owner
|
|
151
|
+
* reports key off it, and the first-admin bootstrap hands seeded rows
|
|
152
|
+
* (owner_id NULL) to the promoted admin. Unlike `created_by` it is
|
|
153
|
+
* editable (`readonly: false`) — ownership transfers; provenance does
|
|
154
|
+
* not. Excluded for `managedBy` / `sys_*` tables and any object that
|
|
155
|
+
* opts out via `ownership: 'org' | 'none'` (Dataverse-style: a
|
|
156
|
+
* catalog/junction table that has no per-record owner). Forgetting the
|
|
157
|
+
* opt-out is harmless (a spare nullable column), whereas forgetting to
|
|
158
|
+
* ADD ownership — the failure mode we are eliminating — silently breaks
|
|
159
|
+
* every owner-keyed feature.
|
|
144
160
|
*/
|
|
145
161
|
declare function applySystemFields(schema: ServiceObject, opts: {
|
|
146
162
|
multiTenant: boolean;
|
|
@@ -612,7 +628,7 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
|
|
|
612
628
|
} | {
|
|
613
629
|
language: "js";
|
|
614
630
|
source: string;
|
|
615
|
-
capabilities: ("log" | "api.read" | "api.write" | "crypto.uuid" | "crypto.hash")[];
|
|
631
|
+
capabilities: ("log" | "api.read" | "api.write" | "api.transaction" | "crypto.uuid" | "crypto.hash")[];
|
|
616
632
|
timeoutMs?: number | undefined;
|
|
617
633
|
memoryMb?: number | undefined;
|
|
618
634
|
} | undefined;
|
|
@@ -2664,6 +2680,38 @@ declare class ScopedContext {
|
|
|
2664
2680
|
* does not support transactions.
|
|
2665
2681
|
*/
|
|
2666
2682
|
transaction(callback: (trxCtx: ScopedContext) => Promise<any>): Promise<any>;
|
|
2683
|
+
/**
|
|
2684
|
+
* Resolve the default driver, if it exposes transaction primitives.
|
|
2685
|
+
* Shared by {@link transaction} and the discrete begin/commit/rollback trio.
|
|
2686
|
+
*/
|
|
2687
|
+
private txDriver;
|
|
2688
|
+
/**
|
|
2689
|
+
* Discrete transaction primitives — `begin` / `commit` / `rollback` as three
|
|
2690
|
+
* separate calls, in contrast to {@link transaction}'s single-callback form.
|
|
2691
|
+
*
|
|
2692
|
+
* This trio exists for callers that cannot keep a JS closure on the stack for
|
|
2693
|
+
* the lifetime of the transaction — chiefly the sandbox runner, where the
|
|
2694
|
+
* hook/action body's `ctx.api.transaction(fn)` is driven across many host
|
|
2695
|
+
* event-loop turns via deferred promises. Across those `setImmediate`
|
|
2696
|
+
* boundaries the engine's ambient `txStore` (AsyncLocalStorage) does NOT
|
|
2697
|
+
* survive, so the transaction handle is threaded **explicitly**: `begin`
|
|
2698
|
+
* returns a child ScopedContext carrying `transaction: trx` in its execution
|
|
2699
|
+
* context, and `resolveTx` honors that explicit handle ahead of the ambient
|
|
2700
|
+
* store. Every `object(...)` op on the returned context therefore reuses the
|
|
2701
|
+
* one connection without relying on ALS.
|
|
2702
|
+
*
|
|
2703
|
+
* Returns `null` when the driver has no transaction support — the caller then
|
|
2704
|
+
* runs non-transactionally against `this` (same graceful degrade as
|
|
2705
|
+
* {@link transaction}).
|
|
2706
|
+
*/
|
|
2707
|
+
beginTransaction(): Promise<{
|
|
2708
|
+
ctx: ScopedContext;
|
|
2709
|
+
handle: unknown;
|
|
2710
|
+
} | null>;
|
|
2711
|
+
/** Commit a handle obtained from {@link beginTransaction}. */
|
|
2712
|
+
commitTransaction(handle: unknown): Promise<void>;
|
|
2713
|
+
/** Roll back a handle obtained from {@link beginTransaction}. */
|
|
2714
|
+
rollbackTransaction(handle: unknown): Promise<void>;
|
|
2667
2715
|
get userId(): string | undefined;
|
|
2668
2716
|
get tenantId(): string | undefined;
|
|
2669
2717
|
/** Alias for tenantId — matches ObjectQLContext.spaceId convention */
|
package/dist/index.js
CHANGED
|
@@ -163,7 +163,10 @@ var init_seed_loader = __esm({
|
|
|
163
163
|
const seedIdentity = config.identity;
|
|
164
164
|
const baseEvalCtx = {
|
|
165
165
|
now: seedNow,
|
|
166
|
-
|
|
166
|
+
// `id: null` is a legitimate seed-time state (the owning admin does not
|
|
167
|
+
// exist yet) that the formula EvalContext's `user.id: string` type does
|
|
168
|
+
// not yet model — cast the fallback so `os.user.id` evaluates to null.
|
|
169
|
+
user: seedIdentity?.user ?? { id: null },
|
|
167
170
|
// Fall back to the per-tenant organizationId so `os.org.id` resolves
|
|
168
171
|
// during per-org replay even without an explicit identity.org.
|
|
169
172
|
org: seedIdentity?.org ?? (config.organizationId ? { id: config.organizationId } : void 0),
|
|
@@ -183,7 +186,7 @@ var init_seed_loader = __esm({
|
|
|
183
186
|
targetField: "(expression)",
|
|
184
187
|
attemptedValue: dataset.records[i],
|
|
185
188
|
recordIndex: i,
|
|
186
|
-
message: `Cannot resolve dynamic seed values for ${objectName} record #${i}: ${seedResult.error.message}.
|
|
189
|
+
message: `Cannot resolve dynamic seed values for ${objectName} record #${i}: ${seedResult.error.message}. \`os.user.id\` resolves to null at seed time (the owning admin does not exist yet) and owner-style fields are assigned by the first-admin handoff \u2014 so a required, non-owner field must not depend on it. Provide a literal value or make the field optional.`
|
|
187
190
|
};
|
|
188
191
|
errors.push(error);
|
|
189
192
|
allErrors.push(error);
|
|
@@ -904,6 +907,8 @@ function applySystemFields(schema, opts) {
|
|
|
904
907
|
const tenancyDisabled = schema.tenancy?.enabled === false;
|
|
905
908
|
const wantTenant = sf?.tenant !== false && !tenancyDisabled;
|
|
906
909
|
const wantAudit = sf?.audit !== false;
|
|
910
|
+
const ownership = schema.ownership;
|
|
911
|
+
const wantOwner = ownership !== "org" && ownership !== "none" && !schema.managedBy && !schema.name.startsWith("sys_");
|
|
907
912
|
const additions = {};
|
|
908
913
|
if (wantTenant && !schema.fields?.organization_id) {
|
|
909
914
|
additions.organization_id = {
|
|
@@ -962,6 +967,17 @@ function applySystemFields(schema, opts) {
|
|
|
962
967
|
};
|
|
963
968
|
}
|
|
964
969
|
}
|
|
970
|
+
if (wantOwner && !schema.fields?.owner_id) {
|
|
971
|
+
additions.owner_id = {
|
|
972
|
+
type: "lookup",
|
|
973
|
+
reference: "sys_user",
|
|
974
|
+
label: "Owner",
|
|
975
|
+
required: false,
|
|
976
|
+
readonly: false,
|
|
977
|
+
system: true,
|
|
978
|
+
description: "Record owner (auto-stamped to the creating user on insert; reassignable). Drives owner-scoped views, reports and notifications."
|
|
979
|
+
};
|
|
980
|
+
}
|
|
965
981
|
if (Object.keys(additions).length === 0) return schema;
|
|
966
982
|
return {
|
|
967
983
|
...schema,
|
|
@@ -7084,7 +7100,7 @@ function aggregateBucket(rows, aggregations) {
|
|
|
7084
7100
|
const alias = agg.alias;
|
|
7085
7101
|
const fn = agg.function;
|
|
7086
7102
|
if (fn === "count") {
|
|
7087
|
-
if (!agg.field) {
|
|
7103
|
+
if (!agg.field || agg.field === "*") {
|
|
7088
7104
|
out[alias] = rows.length;
|
|
7089
7105
|
} else {
|
|
7090
7106
|
out[alias] = rows.reduce(
|
|
@@ -9507,6 +9523,58 @@ var ScopedContext = class _ScopedContext {
|
|
|
9507
9523
|
throw error;
|
|
9508
9524
|
}
|
|
9509
9525
|
}
|
|
9526
|
+
/**
|
|
9527
|
+
* Resolve the default driver, if it exposes transaction primitives.
|
|
9528
|
+
* Shared by {@link transaction} and the discrete begin/commit/rollback trio.
|
|
9529
|
+
*/
|
|
9530
|
+
txDriver() {
|
|
9531
|
+
const engine = this.engine;
|
|
9532
|
+
const driver = engine.defaultDriver ? engine.drivers?.get(engine.defaultDriver) : void 0;
|
|
9533
|
+
return driver?.beginTransaction ? driver : void 0;
|
|
9534
|
+
}
|
|
9535
|
+
/**
|
|
9536
|
+
* Discrete transaction primitives — `begin` / `commit` / `rollback` as three
|
|
9537
|
+
* separate calls, in contrast to {@link transaction}'s single-callback form.
|
|
9538
|
+
*
|
|
9539
|
+
* This trio exists for callers that cannot keep a JS closure on the stack for
|
|
9540
|
+
* the lifetime of the transaction — chiefly the sandbox runner, where the
|
|
9541
|
+
* hook/action body's `ctx.api.transaction(fn)` is driven across many host
|
|
9542
|
+
* event-loop turns via deferred promises. Across those `setImmediate`
|
|
9543
|
+
* boundaries the engine's ambient `txStore` (AsyncLocalStorage) does NOT
|
|
9544
|
+
* survive, so the transaction handle is threaded **explicitly**: `begin`
|
|
9545
|
+
* returns a child ScopedContext carrying `transaction: trx` in its execution
|
|
9546
|
+
* context, and `resolveTx` honors that explicit handle ahead of the ambient
|
|
9547
|
+
* store. Every `object(...)` op on the returned context therefore reuses the
|
|
9548
|
+
* one connection without relying on ALS.
|
|
9549
|
+
*
|
|
9550
|
+
* Returns `null` when the driver has no transaction support — the caller then
|
|
9551
|
+
* runs non-transactionally against `this` (same graceful degrade as
|
|
9552
|
+
* {@link transaction}).
|
|
9553
|
+
*/
|
|
9554
|
+
async beginTransaction() {
|
|
9555
|
+
const driver = this.txDriver();
|
|
9556
|
+
if (!driver) return null;
|
|
9557
|
+
const trx = await driver.beginTransaction();
|
|
9558
|
+
const ctx = new _ScopedContext(
|
|
9559
|
+
{ ...this.executionContext, transaction: trx },
|
|
9560
|
+
this.engine
|
|
9561
|
+
);
|
|
9562
|
+
return { ctx, handle: trx };
|
|
9563
|
+
}
|
|
9564
|
+
/** Commit a handle obtained from {@link beginTransaction}. */
|
|
9565
|
+
async commitTransaction(handle) {
|
|
9566
|
+
const driver = this.txDriver();
|
|
9567
|
+
if (!driver) return;
|
|
9568
|
+
if (driver.commit) await driver.commit(handle);
|
|
9569
|
+
else if (driver.commitTransaction) await driver.commitTransaction(handle);
|
|
9570
|
+
}
|
|
9571
|
+
/** Roll back a handle obtained from {@link beginTransaction}. */
|
|
9572
|
+
async rollbackTransaction(handle) {
|
|
9573
|
+
const driver = this.txDriver();
|
|
9574
|
+
if (!driver) return;
|
|
9575
|
+
if (driver.rollback) await driver.rollback(handle);
|
|
9576
|
+
else if (driver.rollbackTransaction) await driver.rollbackTransaction(handle);
|
|
9577
|
+
}
|
|
9510
9578
|
get userId() {
|
|
9511
9579
|
return this.executionContext.userId;
|
|
9512
9580
|
}
|