@forge-cms/runtime 0.5.0 → 0.6.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/auth-managed.d.ts +19 -0
- package/dist/auth-managed.d.ts.map +1 -0
- package/dist/auth-managed.js +24 -0
- package/dist/auth-managed.js.map +1 -0
- package/dist/concurrency.d.ts +8 -0
- package/dist/concurrency.d.ts.map +1 -0
- package/dist/concurrency.js +15 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/context.d.ts +5 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/errors.d.ts +29 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +38 -0
- package/dist/errors.js.map +1 -1
- package/dist/files.d.ts +15 -1
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +55 -14
- package/dist/files.js.map +1 -1
- package/dist/globals.d.ts +21 -3
- package/dist/globals.d.ts.map +1 -1
- package/dist/globals.js +216 -28
- package/dist/globals.js.map +1 -1
- package/dist/handlers.d.ts +3 -2
- package/dist/handlers.d.ts.map +1 -1
- package/dist/handlers.js +36 -47
- package/dist/handlers.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/localization.d.ts +17 -0
- package/dist/localization.d.ts.map +1 -1
- package/dist/localization.js +56 -0
- package/dist/localization.js.map +1 -1
- package/dist/operations.d.ts +51 -12
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +728 -125
- package/dist/operations.js.map +1 -1
- package/dist/relation-integrity.d.ts +31 -13
- package/dist/relation-integrity.d.ts.map +1 -1
- package/dist/relation-integrity.js +62 -6
- package/dist/relation-integrity.js.map +1 -1
- package/dist/relation-lifecycle.d.ts +95 -0
- package/dist/relation-lifecycle.d.ts.map +1 -0
- package/dist/relation-lifecycle.js +424 -0
- package/dist/relation-lifecycle.js.map +1 -0
- package/dist/runtime.d.ts +21 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +112 -20
- package/dist/runtime.js.map +1 -1
- package/dist/storage-intents.d.ts +96 -0
- package/dist/storage-intents.d.ts.map +1 -0
- package/dist/storage-intents.js +217 -0
- package/dist/storage-intents.js.map +1 -0
- package/dist/system-fields.d.ts +27 -0
- package/dist/system-fields.d.ts.map +1 -0
- package/dist/system-fields.js +74 -0
- package/dist/system-fields.js.map +1 -0
- package/dist/versions.d.ts +73 -1
- package/dist/versions.d.ts.map +1 -1
- package/dist/versions.js +204 -17
- package/dist/versions.js.map +1 -1
- package/package.json +9 -7
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { OperationContext } from './context.js';
|
|
2
|
+
/**
|
|
3
|
+
* Is `slug` a collection whose lifecycle the configured `AuthAdapter` owns (spec 061)? Asked of the
|
|
4
|
+
* adapter through its optional `managesCollection` — the runtime never inspects a concrete adapter or a
|
|
5
|
+
* slug convention, so an adapter that omits the method (or a composite of such adapters) restricts
|
|
6
|
+
* nothing.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isAuthManagedCollection(ctx: OperationContext, slug: string): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* The generic-mutation boundary. Called first thing by `create`/`update`/`deleteDocumentInternal`/
|
|
11
|
+
* `restoreVersion` — before hooks, access checks and any read or write — so the answer is the same for
|
|
12
|
+
* every caller (`overrideAccess: true` bypasses *authorization*, never the auth subsystem's
|
|
13
|
+
* data-integrity invariants) and for a document that does not exist, and a refusal has no side effects.
|
|
14
|
+
* The message deliberately does not distinguish create/update/delete — the boundary is the same for all
|
|
15
|
+
* three, and the caller's fix (use the auth adapter's own operations) does not depend on which one this
|
|
16
|
+
* was.
|
|
17
|
+
*/
|
|
18
|
+
export declare function assertNotAuthManaged(ctx: OperationContext, slug: string): void;
|
|
19
|
+
//# sourceMappingURL=auth-managed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-managed.d.ts","sourceRoot":"","sources":["../src/auth-managed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpF;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAE9E"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { AuthManagedCollectionError } from './errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Is `slug` a collection whose lifecycle the configured `AuthAdapter` owns (spec 061)? Asked of the
|
|
4
|
+
* adapter through its optional `managesCollection` — the runtime never inspects a concrete adapter or a
|
|
5
|
+
* slug convention, so an adapter that omits the method (or a composite of such adapters) restricts
|
|
6
|
+
* nothing.
|
|
7
|
+
*/
|
|
8
|
+
export function isAuthManagedCollection(ctx, slug) {
|
|
9
|
+
return ctx.adapters.auth.managesCollection?.(slug) === true;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The generic-mutation boundary. Called first thing by `create`/`update`/`deleteDocumentInternal`/
|
|
13
|
+
* `restoreVersion` — before hooks, access checks and any read or write — so the answer is the same for
|
|
14
|
+
* every caller (`overrideAccess: true` bypasses *authorization*, never the auth subsystem's
|
|
15
|
+
* data-integrity invariants) and for a document that does not exist, and a refusal has no side effects.
|
|
16
|
+
* The message deliberately does not distinguish create/update/delete — the boundary is the same for all
|
|
17
|
+
* three, and the caller's fix (use the auth adapter's own operations) does not depend on which one this
|
|
18
|
+
* was.
|
|
19
|
+
*/
|
|
20
|
+
export function assertNotAuthManaged(ctx, slug) {
|
|
21
|
+
if (isAuthManagedCollection(ctx, slug))
|
|
22
|
+
throw new AuthManagedCollectionError(slug);
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=auth-managed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-managed.js","sourceRoot":"","sources":["../src/auth-managed.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,GAAqB,EAAE,IAAY;IACzE,OAAO,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;AAC9D,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAqB,EAAE,IAAY;IACtE,IAAI,uBAAuB,CAAC,GAAG,EAAE,IAAI,CAAC;QAAE,MAAM,IAAI,0BAA0B,CAAC,IAAI,CAAC,CAAC;AACrF,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A compare-and-set on `updated_at` is only as fine as the millisecond the adapters stamp. Before a CAS
|
|
3
|
+
* write, wait until the clock has passed the stamp that was read, so this write's own stamp is strictly
|
|
4
|
+
* later: a concurrent CAS writer that read the same row then cannot match it (spec 066 review). Bounded:
|
|
5
|
+
* at most a few milliseconds. Clock skew *between* processes is outside what this can see.
|
|
6
|
+
*/
|
|
7
|
+
export declare function afterStamp(stamp: unknown): Promise<void>;
|
|
8
|
+
//# sourceMappingURL=concurrency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"concurrency.d.ts","sourceRoot":"","sources":["../src/concurrency.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAM9D"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A compare-and-set on `updated_at` is only as fine as the millisecond the adapters stamp. Before a CAS
|
|
3
|
+
* write, wait until the clock has passed the stamp that was read, so this write's own stamp is strictly
|
|
4
|
+
* later: a concurrent CAS writer that read the same row then cannot match it (spec 066 review). Bounded:
|
|
5
|
+
* at most a few milliseconds. Clock skew *between* processes is outside what this can see.
|
|
6
|
+
*/
|
|
7
|
+
export async function afterStamp(stamp) {
|
|
8
|
+
const seen = typeof stamp === 'string' ? Date.parse(stamp) : NaN;
|
|
9
|
+
if (Number.isNaN(seen))
|
|
10
|
+
return;
|
|
11
|
+
for (let i = 0; i < 20 && Date.now() <= seen; i++) {
|
|
12
|
+
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=concurrency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"concurrency.js","sourceRoot":"","sources":["../src/concurrency.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,KAAc;IAC7C,MAAM,IAAI,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IACjE,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO;IAC/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;QAClD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;IACzD,CAAC;AACH,CAAC"}
|
package/dist/context.d.ts
CHANGED
|
@@ -12,5 +12,10 @@ export interface OperationContext {
|
|
|
12
12
|
getCollection(slug: string): CollectionDefinition | undefined;
|
|
13
13
|
getGlobal(slug: string): GlobalDefinition | undefined;
|
|
14
14
|
getCollections(): readonly CollectionDefinition[];
|
|
15
|
+
/**
|
|
16
|
+
* Every registered global. Optional so a structural implementation written before spec 064 still
|
|
17
|
+
* type-checks; without it, relation integrity cannot see references held by globals.
|
|
18
|
+
*/
|
|
19
|
+
getGlobals?(): readonly GlobalDefinition[];
|
|
15
20
|
}
|
|
16
21
|
//# sourceMappingURL=context.d.ts.map
|
package/dist/context.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC9B,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAAC;IAC9D,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAAC;IACtD,cAAc,IAAI,SAAS,oBAAoB,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC9B,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAAC;IAC9D,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAAC;IACtD,cAAc,IAAI,SAAS,oBAAoB,EAAE,CAAC;IAClD;;;OAGG;IACH,UAAU,CAAC,IAAI,SAAS,gBAAgB,EAAE,CAAC;CAC5C"}
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ValidationError } from '@forge-cms/core';
|
|
2
|
-
export type ForgeErrorCode = 'NOT_FOUND' | 'INVALID_INPUT' | 'VALIDATION_ERROR' | 'UNAUTHORIZED' | 'FORBIDDEN' | 'UNKNOWN_FIELD' | 'INVALID_QUERY' | 'UNIQUE_CONSTRAINT' | 'INTERNAL_ERROR';
|
|
2
|
+
export type ForgeErrorCode = 'NOT_FOUND' | 'INVALID_INPUT' | 'VALIDATION_ERROR' | 'UNAUTHORIZED' | 'FORBIDDEN' | 'UNKNOWN_FIELD' | 'INVALID_QUERY' | 'UNIQUE_CONSTRAINT' | 'AUTH_MANAGED_COLLECTION' | 'CONCURRENT_MODIFICATION' | 'INTERNAL_ERROR';
|
|
3
3
|
export declare class ForgeError extends Error {
|
|
4
4
|
readonly status: number;
|
|
5
5
|
readonly code: ForgeErrorCode;
|
|
@@ -47,6 +47,34 @@ export declare class UniqueConstraintError extends ForgeError {
|
|
|
47
47
|
readonly fields: string[];
|
|
48
48
|
constructor(collection: string, fields: string[]);
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* A generic `create`/`update`/`delete` targeted a collection whose lifecycle the configured
|
|
52
|
+
* `AuthAdapter` owns (`AuthAdapter.managesCollection`, spec 061). Those mutations would bypass the
|
|
53
|
+
* adapter's invariants — first-admin provisioning, last-admin protection, password hashing, email
|
|
54
|
+
* normalisation, session versioning — so they are refused for every caller, trusted (`overrideAccess:
|
|
55
|
+
* true`) or not. Deliberately says nothing about which adapter or how; the way out is the auth
|
|
56
|
+
* adapter's own user-management operations.
|
|
57
|
+
*/
|
|
58
|
+
export declare class AuthManagedCollectionError extends ForgeError {
|
|
59
|
+
readonly collection: string;
|
|
60
|
+
constructor(collection: string);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A versioned document mutation lost a race (spec 062): another write committed a new version of the
|
|
64
|
+
* same document after this one read it, so this one's document write and snapshot were rolled back
|
|
65
|
+
* together — nothing was written. Forge never retries it automatically (`before*` hooks may already
|
|
66
|
+
* have run); re-read the document and submit again. Deliberately names the public collection slug,
|
|
67
|
+
* never the internal `_versions_*` table whose unique index detected the conflict.
|
|
68
|
+
*
|
|
69
|
+
* Spec 064 reuses it for relation lifecycle conflicts — a referenced document deleted while a write was
|
|
70
|
+
* in progress, or a relation delete whose graph changed before it committed — with a `message` saying
|
|
71
|
+
* so. The meaning is the same: another request changed what this one depended on; nothing was written.
|
|
72
|
+
*/
|
|
73
|
+
export declare class ConcurrentModificationError extends ForgeError {
|
|
74
|
+
readonly collection: string;
|
|
75
|
+
readonly id: string;
|
|
76
|
+
constructor(collection: string, id: string, message?: string);
|
|
77
|
+
}
|
|
50
78
|
export declare function isForgeError(err: unknown): err is ForgeError;
|
|
51
79
|
export interface ForgeApiErrorBody {
|
|
52
80
|
error: {
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,MAAM,cAAc,GACtB,WAAW,GACX,eAAe,GACf,kBAAkB,GAClB,cAAc,GACd,WAAW,GACX,eAAe,GACf,eAAe,GACf,mBAAmB,GACnB,gBAAgB,CAAC;AAErB,qBAAa,UAAW,SAAQ,KAAK;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;gBAElB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc;CAMlE;AAED,qBAAa,aAAc,SAAQ,UAAU;gBAC/B,OAAO,EAAE,MAAM;CAG5B;AAED,qBAAa,iBAAkB,SAAQ,UAAU;gBACnC,OAAO,EAAE,MAAM;CAG5B;AAED,qBAAa,iBAAkB,SAAQ,UAAU;gBACnC,OAAO,EAAE,MAAM;CAG5B;AAED,qBAAa,iBAAkB,SAAQ,UAAU;gBACnC,OAAO,EAAE,MAAM;CAG5B;AAED,qBAAa,qBAAsB,SAAQ,UAAU;IACnD,QAAQ,CAAC,OAAO,EAAE,eAAe,EAAE,CAAC;gBAExB,OAAO,EAAE,eAAe,EAAE;CAIvC;AAED,qBAAa,iBAAkB,SAAQ,UAAU;gBACnC,OAAO,SAAiB;CAGrC;AAED,qBAAa,iBAAkB,SAAQ,UAAU;gBACnC,OAAO,SAAc;CAGlC;AAED;;;;;GAKG;AACH,qBAAa,SAAU,SAAQ,UAAU;gBAC3B,OAAO,SAAgC;CAGpD;AAED;;;;;GAKG;AACH,qBAAa,qBAAsB,SAAQ,UAAU;IACnD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;gBAEd,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE;CAWjD;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,UAAU,CAE5D;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE;QACL,IAAI,EAAE,cAAc,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC;CACH;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,iBAAiB,CAyBjE"}
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,MAAM,cAAc,GACtB,WAAW,GACX,eAAe,GACf,kBAAkB,GAClB,cAAc,GACd,WAAW,GACX,eAAe,GACf,eAAe,GACf,mBAAmB,GACnB,yBAAyB,GACzB,yBAAyB,GACzB,gBAAgB,CAAC;AAErB,qBAAa,UAAW,SAAQ,KAAK;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;gBAElB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc;CAMlE;AAED,qBAAa,aAAc,SAAQ,UAAU;gBAC/B,OAAO,EAAE,MAAM;CAG5B;AAED,qBAAa,iBAAkB,SAAQ,UAAU;gBACnC,OAAO,EAAE,MAAM;CAG5B;AAED,qBAAa,iBAAkB,SAAQ,UAAU;gBACnC,OAAO,EAAE,MAAM;CAG5B;AAED,qBAAa,iBAAkB,SAAQ,UAAU;gBACnC,OAAO,EAAE,MAAM;CAG5B;AAED,qBAAa,qBAAsB,SAAQ,UAAU;IACnD,QAAQ,CAAC,OAAO,EAAE,eAAe,EAAE,CAAC;gBAExB,OAAO,EAAE,eAAe,EAAE;CAIvC;AAED,qBAAa,iBAAkB,SAAQ,UAAU;gBACnC,OAAO,SAAiB;CAGrC;AAED,qBAAa,iBAAkB,SAAQ,UAAU;gBACnC,OAAO,SAAc;CAGlC;AAED;;;;;GAKG;AACH,qBAAa,SAAU,SAAQ,UAAU;gBAC3B,OAAO,SAAgC;CAGpD;AAED;;;;;GAKG;AACH,qBAAa,qBAAsB,SAAQ,UAAU;IACnD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;gBAEd,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE;CAWjD;AAED;;;;;;;GAOG;AACH,qBAAa,0BAA2B,SAAQ,UAAU;IACxD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAEhB,UAAU,EAAE,MAAM;CAS/B;AAED;;;;;;;;;;GAUG;AACH,qBAAa,2BAA4B,SAAQ,UAAU;IACzD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;gBAER,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM;CAW7D;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,UAAU,CAE5D;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE;QACL,IAAI,EAAE,cAAc,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC;CACH;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,iBAAiB,CAyBjE"}
|
package/dist/errors.js
CHANGED
|
@@ -73,6 +73,44 @@ export class UniqueConstraintError extends ForgeError {
|
|
|
73
73
|
this.fields = fields;
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* A generic `create`/`update`/`delete` targeted a collection whose lifecycle the configured
|
|
78
|
+
* `AuthAdapter` owns (`AuthAdapter.managesCollection`, spec 061). Those mutations would bypass the
|
|
79
|
+
* adapter's invariants — first-admin provisioning, last-admin protection, password hashing, email
|
|
80
|
+
* normalisation, session versioning — so they are refused for every caller, trusted (`overrideAccess:
|
|
81
|
+
* true`) or not. Deliberately says nothing about which adapter or how; the way out is the auth
|
|
82
|
+
* adapter's own user-management operations.
|
|
83
|
+
*/
|
|
84
|
+
export class AuthManagedCollectionError extends ForgeError {
|
|
85
|
+
collection;
|
|
86
|
+
constructor(collection) {
|
|
87
|
+
super(`Collection '${collection}' is managed by the configured auth adapter and cannot be mutated ` +
|
|
88
|
+
`through generic collection CRUD. Use the auth adapter's user-management operations instead.`, 403, 'AUTH_MANAGED_COLLECTION');
|
|
89
|
+
this.collection = collection;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* A versioned document mutation lost a race (spec 062): another write committed a new version of the
|
|
94
|
+
* same document after this one read it, so this one's document write and snapshot were rolled back
|
|
95
|
+
* together — nothing was written. Forge never retries it automatically (`before*` hooks may already
|
|
96
|
+
* have run); re-read the document and submit again. Deliberately names the public collection slug,
|
|
97
|
+
* never the internal `_versions_*` table whose unique index detected the conflict.
|
|
98
|
+
*
|
|
99
|
+
* Spec 064 reuses it for relation lifecycle conflicts — a referenced document deleted while a write was
|
|
100
|
+
* in progress, or a relation delete whose graph changed before it committed — with a `message` saying
|
|
101
|
+
* so. The meaning is the same: another request changed what this one depended on; nothing was written.
|
|
102
|
+
*/
|
|
103
|
+
export class ConcurrentModificationError extends ForgeError {
|
|
104
|
+
collection;
|
|
105
|
+
id;
|
|
106
|
+
constructor(collection, id, message) {
|
|
107
|
+
super(message ??
|
|
108
|
+
`Document '${id}' in '${collection}' was changed by another request while this one was in ` +
|
|
109
|
+
`progress; nothing was written. Reload it and try again.`, 409, 'CONCURRENT_MODIFICATION');
|
|
110
|
+
this.collection = collection;
|
|
111
|
+
this.id = id;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
76
114
|
export function isForgeError(err) {
|
|
77
115
|
return err instanceof ForgeError;
|
|
78
116
|
}
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/files.d.ts
CHANGED
|
@@ -2,7 +2,11 @@ import type { ApiContext } from '@forge-cms/api';
|
|
|
2
2
|
import type { AnyForgeCmsRuntime } from './runtime.js';
|
|
3
3
|
export interface FileHandlerOptions<TEnv = unknown> {
|
|
4
4
|
runtime: AnyForgeCmsRuntime<TEnv>;
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* `cache-control` for a hit served to an **anonymous** caller. Defaults to a conservative minute. A
|
|
7
|
+
* hit for an authenticated caller is always `private, no-store` (spec 067): shared caches must not
|
|
8
|
+
* hand a file one user may read to another who may not.
|
|
9
|
+
*/
|
|
6
10
|
cacheControl?: string;
|
|
7
11
|
}
|
|
8
12
|
/**
|
|
@@ -19,6 +23,16 @@ export interface FileHandlerOptions<TEnv = unknown> {
|
|
|
19
23
|
* handleFile({ request: toWebRequest(event), params: { key: getRouterParam(event, 'key') ?? '' } }, { runtime })
|
|
20
24
|
* );
|
|
21
25
|
* ```
|
|
26
|
+
*
|
|
27
|
+
* **Access (spec 067).** A file is served only as part of the upload document that owns it: the document
|
|
28
|
+
* of the key's collection whose Forge-recorded `_storageKey` is this key. That document must be readable
|
|
29
|
+
* by the caller through the normal read pipeline (collection and row access, draft visibility), exactly
|
|
30
|
+
* as the Local API `findByID` with `overrideAccess: false` allows. It applies the collection's own
|
|
31
|
+
* `access.read`, **not** route-level options such as `requireAuth`/`allowedRoles` — a collection
|
|
32
|
+
* whose reads are gated only at its route must declare `access.read` to gate its files too. A key no
|
|
33
|
+
* document owns, or one whose document the
|
|
34
|
+
* caller cannot read, is the same `404` — it confirms nothing. A public bucket or CDN URL is outside
|
|
35
|
+
* this handler and outside Forge's access control.
|
|
22
36
|
*/
|
|
23
37
|
export declare function handleFile<TEnv = unknown>(context: ApiContext<TEnv>, options: FileHandlerOptions<TEnv>): Promise<Response>;
|
|
24
38
|
//# sourceMappingURL=files.d.ts.map
|
package/dist/files.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAKvD,MAAM,WAAW,kBAAkB,CAAC,IAAI,GAAG,OAAO;IAChD,OAAO,EAAE,kBAAkB,CAAC,IAAI,CAAC,CAAC;IAClC;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,UAAU,CAAC,IAAI,GAAG,OAAO,EAC7C,OAAO,EAAE,UAAU,CAAC,IAAI,CAAC,EACzB,OAAO,EAAE,kBAAkB,CAAC,IAAI,CAAC,GAChC,OAAO,CAAC,QAAQ,CAAC,CA8CnB"}
|
package/dist/files.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { AccessDeniedError, NotFoundError } from './errors.js';
|
|
2
|
+
import { resolveOptionalUser } from './handlers.js';
|
|
3
|
+
import { findStorageOwner } from './storage-intents.js';
|
|
1
4
|
/**
|
|
2
5
|
* Serves a stored file back over HTTP.
|
|
3
6
|
*
|
|
@@ -12,35 +15,73 @@
|
|
|
12
15
|
* handleFile({ request: toWebRequest(event), params: { key: getRouterParam(event, 'key') ?? '' } }, { runtime })
|
|
13
16
|
* );
|
|
14
17
|
* ```
|
|
18
|
+
*
|
|
19
|
+
* **Access (spec 067).** A file is served only as part of the upload document that owns it: the document
|
|
20
|
+
* of the key's collection whose Forge-recorded `_storageKey` is this key. That document must be readable
|
|
21
|
+
* by the caller through the normal read pipeline (collection and row access, draft visibility), exactly
|
|
22
|
+
* as the Local API `findByID` with `overrideAccess: false` allows. It applies the collection's own
|
|
23
|
+
* `access.read`, **not** route-level options such as `requireAuth`/`allowedRoles` — a collection
|
|
24
|
+
* whose reads are gated only at its route must declare `access.read` to gate its files too. A key no
|
|
25
|
+
* document owns, or one whose document the
|
|
26
|
+
* caller cannot read, is the same `404` — it confirms nothing. A public bucket or CDN URL is outside
|
|
27
|
+
* this handler and outside Forge's access control.
|
|
15
28
|
*/
|
|
16
29
|
export async function handleFile(context, options) {
|
|
17
30
|
const raw = context.params?.['key'];
|
|
18
|
-
if (!raw)
|
|
19
|
-
return
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
31
|
+
if (!raw)
|
|
32
|
+
return json({ error: 'Missing file key' }, 400);
|
|
33
|
+
let key;
|
|
34
|
+
try {
|
|
35
|
+
key = decodeURIComponent(raw);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return json({ error: 'Malformed file key' }, 400);
|
|
23
39
|
}
|
|
24
|
-
const
|
|
40
|
+
const { runtime } = options;
|
|
25
41
|
try {
|
|
26
|
-
const
|
|
27
|
-
if (!
|
|
28
|
-
return
|
|
29
|
-
|
|
30
|
-
|
|
42
|
+
const owner = await findStorageOwner(runtime, key);
|
|
43
|
+
if (!owner)
|
|
44
|
+
return notFound(key);
|
|
45
|
+
const user = await resolveOptionalUser(context, runtime);
|
|
46
|
+
try {
|
|
47
|
+
await runtime.findByID({
|
|
48
|
+
collection: owner.collection,
|
|
49
|
+
id: owner.id,
|
|
50
|
+
user,
|
|
51
|
+
overrideAccess: false,
|
|
52
|
+
depth: 0
|
|
31
53
|
});
|
|
32
54
|
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
if (err instanceof NotFoundError || err instanceof AccessDeniedError)
|
|
57
|
+
return notFound(key);
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
const object = await runtime.adapters.storage.get(key);
|
|
61
|
+
if (!object?.body)
|
|
62
|
+
return notFound(key);
|
|
33
63
|
return new Response(object.body, {
|
|
34
64
|
status: 200,
|
|
35
65
|
headers: {
|
|
36
66
|
'content-type': object.contentType ?? 'application/octet-stream',
|
|
37
|
-
'cache-control': options.cacheControl ?? 'public, max-age=60',
|
|
67
|
+
'cache-control': user === null ? (options.cacheControl ?? 'public, max-age=60') : 'private, no-store',
|
|
38
68
|
...(object.size !== undefined && { 'content-length': String(object.size) })
|
|
39
69
|
}
|
|
40
70
|
});
|
|
41
71
|
}
|
|
42
|
-
catch
|
|
43
|
-
|
|
72
|
+
catch {
|
|
73
|
+
// No driver or storage message reaches the client (it can name buckets, tables or credentials).
|
|
74
|
+
return json({ error: 'Failed to read file' }, 500);
|
|
44
75
|
}
|
|
45
76
|
}
|
|
77
|
+
function notFound(key) {
|
|
78
|
+
return json({ error: `File '${key}' not found` }, 404);
|
|
79
|
+
}
|
|
80
|
+
/** Error bodies are never cached: an anonymous `404` must not be served to the owner who may read it. */
|
|
81
|
+
function json(body, status) {
|
|
82
|
+
return new Response(JSON.stringify(body), {
|
|
83
|
+
status,
|
|
84
|
+
headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
|
|
85
|
+
});
|
|
86
|
+
}
|
|
46
87
|
//# sourceMappingURL=files.js.map
|
package/dist/files.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"files.js","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAYxD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,OAAyB,EACzB,OAAiC;IAEjC,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;IACpC,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,EAAE,GAAG,CAAC,CAAC;IAE1D,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAAE,GAAG,CAAC,CAAC;IACpD,CAAC;IACD,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAE5B,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACnD,IAAI,CAAC,KAAK;YAAE,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC;QAEjC,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACzD,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,QAAQ,CAAC;gBACrB,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,EAAE,EAAE,KAAK,CAAC,EAAE;gBACZ,IAAI;gBACJ,cAAc,EAAE,KAAK;gBACrB,KAAK,EAAE,CAAC;aACT,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,aAAa,IAAI,GAAG,YAAY,iBAAiB;gBAAE,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC3F,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACvD,IAAI,CAAC,MAAM,EAAE,IAAI;YAAE,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC;QAExC,OAAO,IAAI,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE;YAC/B,MAAM,EAAE,GAAG;YACX,OAAO,EAAE;gBACP,cAAc,EAAE,MAAM,CAAC,WAAW,IAAI,0BAA0B;gBAChE,eAAe,EACb,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,IAAI,oBAAoB,CAAC,CAAC,CAAC,CAAC,mBAAmB;gBACtF,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,gBAAgB,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;aAC5E;SACF,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,gGAAgG;QAChG,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,EAAE,GAAG,CAAC,CAAC;IACrD,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW;IAC3B,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,SAAS,GAAG,aAAa,EAAE,EAAE,GAAG,CAAC,CAAC;AACzD,CAAC;AAED,yGAAyG;AACzG,SAAS,IAAI,CAAC,IAAa,EAAE,MAAc;IACzC,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE;QACxC,MAAM;QACN,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,eAAe,EAAE,UAAU,EAAE;KAC7E,CAAC,CAAC;AACL,CAAC"}
|
package/dist/globals.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CmsUser } from '@forge-cms/core';
|
|
1
|
+
import type { CmsUser, GlobalDefinition } from '@forge-cms/core';
|
|
2
2
|
import type { DatabaseRecord } from '@forge-cms/db';
|
|
3
3
|
import type { OperationContext } from './context.js';
|
|
4
4
|
export interface GlobalBaseArgs {
|
|
@@ -8,17 +8,35 @@ export interface GlobalBaseArgs {
|
|
|
8
8
|
}
|
|
9
9
|
export interface GetGlobalArgs extends GlobalBaseArgs {
|
|
10
10
|
depth?: 0 | 1;
|
|
11
|
+
/** Resolve `localized` fields to this locale (with fallback), for a global declaring `locales` (spec 066). */
|
|
12
|
+
locale?: string;
|
|
11
13
|
}
|
|
12
14
|
export interface UpdateGlobalArgs extends GlobalBaseArgs {
|
|
13
15
|
data: Record<string, unknown>;
|
|
16
|
+
/** Write `localized` fields into this locale only, keeping the others (spec 066). Must be one of the global's `locales`. */
|
|
17
|
+
locale?: string;
|
|
14
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* The global options that exist on the shared collection types but can never apply to a singleton that
|
|
21
|
+
* is never created through `create` nor deleted (spec 066): delete hooks, and `create`/`delete` access
|
|
22
|
+
* rules. Refused at startup instead of being accepted and silently ignored. One message per problem.
|
|
23
|
+
* (Localized fields of globals are checked with collections', by `validateLocalizationSchema`.)
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateGlobalSchema(globals: readonly GlobalDefinition[]): string[];
|
|
15
26
|
/**
|
|
16
27
|
* Reads the singleton global document. Returns `null` if the global has never been written.
|
|
17
28
|
*/
|
|
18
29
|
export declare function getGlobal(ctx: OperationContext, args: GetGlobalArgs): Promise<DatabaseRecord | null>;
|
|
19
30
|
/**
|
|
20
|
-
* Creates or updates the singleton global document. Unlike collections, globals always have
|
|
21
|
-
*
|
|
31
|
+
* Creates or updates the singleton global document. Unlike collections, globals always have exactly one
|
|
32
|
+
* document — the first write creates it, later writes update it.
|
|
33
|
+
*
|
|
34
|
+
* Later writes are **partial**, exactly like a collection `update()` (spec 066): omitted fields keep
|
|
35
|
+
* their stored values (defaults and the draft status apply to the first write only), validation runs on
|
|
36
|
+
* the merged document, and a `locale` write changes that locale of each `localized` field and keeps the
|
|
37
|
+
* others. A query-returning `access.update` rule must match the stored row, and cannot authorize the
|
|
38
|
+
* first write (there is no row to match). Two simultaneous first writes: one commits, the other gets
|
|
39
|
+
* `409 CONCURRENT_MODIFICATION` with nothing written.
|
|
22
40
|
*/
|
|
23
41
|
export declare function updateGlobal(ctx: OperationContext, args: UpdateGlobalArgs): Promise<DatabaseRecord>;
|
|
24
42
|
//# sourceMappingURL=globals.d.ts.map
|
package/dist/globals.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"globals.d.ts","sourceRoot":"","sources":["../src/globals.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"globals.d.ts","sourceRoot":"","sources":["../src/globals.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAwB,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACvF,OAAO,KAAK,EAAwB,cAAc,EAAiB,MAAM,eAAe,CAAC;AAKzF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAuCrD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,aAAc,SAAQ,cAAc;IACnD,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IACd,8GAA8G;IAC9G,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAiB,SAAQ,cAAc;IACtD,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,4HAA4H;IAC5H,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,GAAG,MAAM,EAAE,CAsBnF;AAiGD;;GAEG;AACH,wBAAsB,SAAS,CAC7B,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CA+DhC;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,cAAc,CAAC,CAyJzB"}
|