@dyrected/core 2.5.59 → 2.5.60
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/app-config-C0twGIaF.d.cts +1597 -0
- package/dist/app-config-C0twGIaF.d.ts +1597 -0
- package/dist/app-config-CstocO9M.d.cts +1580 -0
- package/dist/app-config-CstocO9M.d.ts +1580 -0
- package/dist/app-config-DIwvzbpe.d.cts +1555 -0
- package/dist/app-config-DIwvzbpe.d.ts +1555 -0
- package/dist/app-config-Dkndq5ni.d.cts +1580 -0
- package/dist/app-config-Dkndq5ni.d.ts +1580 -0
- package/dist/app-config-P-SF5nnR.d.cts +1568 -0
- package/dist/app-config-P-SF5nnR.d.ts +1568 -0
- package/dist/chunk-2WODKOUB.js +1627 -0
- package/dist/chunk-3CMG2BH2.js +1618 -0
- package/dist/chunk-DCY7QHRB.js +1637 -0
- package/dist/chunk-EOW4YMAV.js +1637 -0
- package/dist/chunk-JFA22YFB.js +1625 -0
- package/dist/chunk-WF5A6PDJ.js +1618 -0
- package/dist/index.cjs +141 -42
- package/dist/index.d.cts +36 -3
- package/dist/index.d.ts +36 -3
- package/dist/index.js +8 -1
- package/dist/server.cjs +344 -51
- package/dist/server.d.cts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +212 -10
- package/package.json +1 -1
package/dist/server.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as hono_types from 'hono/types';
|
|
2
2
|
import * as hono from 'hono';
|
|
3
3
|
import { Hono, Context } from 'hono';
|
|
4
|
-
import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './app-config-
|
|
4
|
+
import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './app-config-C0twGIaF.cjs';
|
|
5
5
|
import * as hono_utils_http_status from 'hono/utils/http-status';
|
|
6
6
|
import * as hono_utils_types from 'hono/utils/types';
|
|
7
7
|
import 'lucide-react';
|
package/dist/server.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as hono_types from 'hono/types';
|
|
2
2
|
import * as hono from 'hono';
|
|
3
3
|
import { Hono, Context } from 'hono';
|
|
4
|
-
import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './app-config-
|
|
4
|
+
import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './app-config-C0twGIaF.js';
|
|
5
5
|
import * as hono_utils_http_status from 'hono/utils/http-status';
|
|
6
6
|
import * as hono_utils_types from 'hono/utils/types';
|
|
7
7
|
import 'lucide-react';
|
package/dist/server.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
runCollectionHooks,
|
|
14
14
|
saveWorkflowDraft,
|
|
15
15
|
transitionWorkflow
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-2WODKOUB.js";
|
|
17
17
|
|
|
18
18
|
// src/app.ts
|
|
19
19
|
import { Hono } from "hono";
|
|
@@ -374,12 +374,16 @@ async function resolveAccess(config, access, args) {
|
|
|
374
374
|
}
|
|
375
375
|
}
|
|
376
376
|
if (isNamedAccessPolicy(access)) {
|
|
377
|
-
const
|
|
378
|
-
if (
|
|
377
|
+
const policy = config.accessPolicies?.[access.policy];
|
|
378
|
+
if (policy === void 0) {
|
|
379
379
|
console.error(`[dyrected/core] Unknown access policy "${access.policy}".`);
|
|
380
380
|
return false;
|
|
381
381
|
}
|
|
382
|
+
if (typeof policy === "string" || typeof policy === "boolean") {
|
|
383
|
+
return evaluateAccess(policy, args);
|
|
384
|
+
}
|
|
382
385
|
try {
|
|
386
|
+
const resolver = policy;
|
|
383
387
|
return await resolver({ ...args, params: access.params });
|
|
384
388
|
} catch (err) {
|
|
385
389
|
console.error(`[dyrected/core] Access policy "${access.policy}" failed:`, err);
|
|
@@ -487,14 +491,15 @@ async function applyFieldWriteAccess(context, data) {
|
|
|
487
491
|
continue;
|
|
488
492
|
}
|
|
489
493
|
if (!field.name || !(field.name in result)) continue;
|
|
490
|
-
const
|
|
494
|
+
const writeRule = context.operation === "create" ? field.access?.create ?? field.access?.update : field.access?.update;
|
|
495
|
+
const canWrite = await resolveBooleanAccess(context.config, writeRule, {
|
|
491
496
|
user: context.user,
|
|
492
497
|
req: context.req,
|
|
493
498
|
doc: context.doc,
|
|
494
499
|
data: context.data,
|
|
495
500
|
id: resolveFieldAccessId(context)
|
|
496
501
|
});
|
|
497
|
-
if (!
|
|
502
|
+
if (!canWrite) {
|
|
498
503
|
delete result[field.name];
|
|
499
504
|
continue;
|
|
500
505
|
}
|
|
@@ -608,7 +613,7 @@ var CollectionController = class {
|
|
|
608
613
|
const readonlyDb = createReadonlyDb(db);
|
|
609
614
|
const limit = Number(c.req.query("limit")) || 10;
|
|
610
615
|
const page = Number(c.req.query("page")) || 1;
|
|
611
|
-
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) :
|
|
616
|
+
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 1;
|
|
612
617
|
const sort = c.req.query("sort") || void 0;
|
|
613
618
|
const user = c.get("user");
|
|
614
619
|
let where = void 0;
|
|
@@ -724,7 +729,7 @@ var CollectionController = class {
|
|
|
724
729
|
}
|
|
725
730
|
const readonlyDb = createReadonlyDb(db);
|
|
726
731
|
const id = c.req.param("id");
|
|
727
|
-
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) :
|
|
732
|
+
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 1;
|
|
728
733
|
const user = c.get("user");
|
|
729
734
|
if (!id) return c.json({ message: "Missing ID" }, 400);
|
|
730
735
|
let doc = await db.findOne({ collection: this.collection.slug, id });
|
|
@@ -813,7 +818,8 @@ var CollectionController = class {
|
|
|
813
818
|
fields: this.collection.fields,
|
|
814
819
|
user,
|
|
815
820
|
req: this.toHookRequestContext(c),
|
|
816
|
-
data
|
|
821
|
+
data,
|
|
822
|
+
operation: "create"
|
|
817
823
|
}, data);
|
|
818
824
|
data = await executeFieldBeforeChange(this.collection.fields, data, null, user, readonlyDb);
|
|
819
825
|
data = await runCollectionHooks(this.collection.hooks?.beforeChange, {
|
|
@@ -908,7 +914,8 @@ var CollectionController = class {
|
|
|
908
914
|
fields: this.collection.fields,
|
|
909
915
|
user,
|
|
910
916
|
req: this.toHookRequestContext(c),
|
|
911
|
-
data
|
|
917
|
+
data,
|
|
918
|
+
operation: "create"
|
|
912
919
|
}, data);
|
|
913
920
|
data = await executeFieldBeforeChange(this.collection.fields, data, null, user, readonlyDb);
|
|
914
921
|
data = await runCollectionHooks(this.collection.hooks?.beforeChange, {
|
|
@@ -1330,7 +1337,7 @@ var GlobalController = class {
|
|
|
1330
1337
|
const db = config.db;
|
|
1331
1338
|
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
1332
1339
|
const readonlyDb = createReadonlyDb(db);
|
|
1333
|
-
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) :
|
|
1340
|
+
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 1;
|
|
1334
1341
|
const user = c.get("user");
|
|
1335
1342
|
let query = void 0;
|
|
1336
1343
|
const beforeReadResult = await runCollectionHooks(this.global.hooks?.beforeRead, {
|
|
@@ -2554,6 +2561,150 @@ var PreviewController = class {
|
|
|
2554
2561
|
}
|
|
2555
2562
|
};
|
|
2556
2563
|
|
|
2564
|
+
// src/controllers/audit.controller.ts
|
|
2565
|
+
var AuditController = class {
|
|
2566
|
+
parseQuery(c) {
|
|
2567
|
+
const limit = Math.min(Number(c.req.query("limit")) || 50, 100);
|
|
2568
|
+
const page = Math.max(Number(c.req.query("page")) || 1, 1);
|
|
2569
|
+
const sort = c.req.query("sort") || "-timestamp";
|
|
2570
|
+
const whereRaw = c.req.query("where");
|
|
2571
|
+
if (!whereRaw) {
|
|
2572
|
+
return { limit, page, sort };
|
|
2573
|
+
}
|
|
2574
|
+
try {
|
|
2575
|
+
return {
|
|
2576
|
+
limit,
|
|
2577
|
+
page,
|
|
2578
|
+
sort,
|
|
2579
|
+
where: JSON.parse(decodeURIComponent(whereRaw))
|
|
2580
|
+
};
|
|
2581
|
+
} catch {
|
|
2582
|
+
return { limit, page, sort };
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
emptyResult(limit, page) {
|
|
2586
|
+
return {
|
|
2587
|
+
docs: [],
|
|
2588
|
+
total: 0,
|
|
2589
|
+
limit,
|
|
2590
|
+
page,
|
|
2591
|
+
totalPages: 0,
|
|
2592
|
+
hasNextPage: false,
|
|
2593
|
+
hasPrevPage: page > 1
|
|
2594
|
+
};
|
|
2595
|
+
}
|
|
2596
|
+
async collectAccessibleDocumentIds(db, collection, constraint) {
|
|
2597
|
+
const ids = [];
|
|
2598
|
+
const limit = 100;
|
|
2599
|
+
let page = 1;
|
|
2600
|
+
while (true) {
|
|
2601
|
+
const result = await db.find({
|
|
2602
|
+
collection: collection.slug,
|
|
2603
|
+
where: constraint,
|
|
2604
|
+
limit,
|
|
2605
|
+
page
|
|
2606
|
+
});
|
|
2607
|
+
for (const doc of result.docs) {
|
|
2608
|
+
if (typeof doc?.id === "string" && doc.id.length > 0) {
|
|
2609
|
+
ids.push(doc.id);
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
if (!result.hasNextPage || page >= result.totalPages) {
|
|
2613
|
+
break;
|
|
2614
|
+
}
|
|
2615
|
+
page += 1;
|
|
2616
|
+
}
|
|
2617
|
+
return ids;
|
|
2618
|
+
}
|
|
2619
|
+
async buildCollectionAuditScope(c, collection) {
|
|
2620
|
+
const config = c.get("config");
|
|
2621
|
+
const auditAccess = collection.access?.readAudit ?? collection.access?.read;
|
|
2622
|
+
const access = await resolveCollectionAccess(
|
|
2623
|
+
config,
|
|
2624
|
+
collection.slug,
|
|
2625
|
+
"read",
|
|
2626
|
+
auditAccess,
|
|
2627
|
+
{
|
|
2628
|
+
user: c.get("user"),
|
|
2629
|
+
req: toHookRequestContext(c.req)
|
|
2630
|
+
}
|
|
2631
|
+
);
|
|
2632
|
+
if (!access.allowed) {
|
|
2633
|
+
return { denied: true, where: {} };
|
|
2634
|
+
}
|
|
2635
|
+
let where = { collection: { equals: collection.slug } };
|
|
2636
|
+
if (access.constraint) {
|
|
2637
|
+
const ids = await this.collectAccessibleDocumentIds(config.db, collection, access.constraint);
|
|
2638
|
+
where = mergeWhereConstraint(where, { documentId: { in: ids } });
|
|
2639
|
+
}
|
|
2640
|
+
return { denied: false, where };
|
|
2641
|
+
}
|
|
2642
|
+
async getVisibleCollections(c) {
|
|
2643
|
+
const config = c.get("config");
|
|
2644
|
+
const collections = /* @__PURE__ */ new Map();
|
|
2645
|
+
for (const collection of config.collections) {
|
|
2646
|
+
collections.set(collection.slug, collection);
|
|
2647
|
+
}
|
|
2648
|
+
const siteId = c.req.header("X-Site-Id") || c.get("siteId");
|
|
2649
|
+
if (config.onSchemaFetch && siteId) {
|
|
2650
|
+
const dynamic = await config.onSchemaFetch(siteId);
|
|
2651
|
+
for (const collection of dynamic.collections || []) {
|
|
2652
|
+
if (!collections.has(collection.slug)) {
|
|
2653
|
+
collections.set(collection.slug, collection);
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
return Array.from(collections.values());
|
|
2658
|
+
}
|
|
2659
|
+
async findForCollection(c, collection) {
|
|
2660
|
+
const config = c.get("config");
|
|
2661
|
+
if (!config.db) return c.json({ message: "Database not configured" }, 500);
|
|
2662
|
+
if (!collection.audit) return c.json({ message: "Audit is not enabled for this collection" }, 404);
|
|
2663
|
+
const query = this.parseQuery(c);
|
|
2664
|
+
const scope = await this.buildCollectionAuditScope(c, collection);
|
|
2665
|
+
if (scope.denied) {
|
|
2666
|
+
return c.json({ error: true, message: `Access denied: read on ${collection.slug}` }, 403);
|
|
2667
|
+
}
|
|
2668
|
+
const where = query.where ? mergeWhereConstraint(scope.where, query.where) : scope.where;
|
|
2669
|
+
const result = await config.db.find({
|
|
2670
|
+
collection: "__audit",
|
|
2671
|
+
where,
|
|
2672
|
+
limit: query.limit,
|
|
2673
|
+
page: query.page,
|
|
2674
|
+
sort: query.sort
|
|
2675
|
+
});
|
|
2676
|
+
return c.json(result);
|
|
2677
|
+
}
|
|
2678
|
+
async findAll(c) {
|
|
2679
|
+
const config = c.get("config");
|
|
2680
|
+
if (!config.db) return c.json({ message: "Database not configured" }, 500);
|
|
2681
|
+
const query = this.parseQuery(c);
|
|
2682
|
+
const collections = (await this.getVisibleCollections(c)).filter((collection) => collection.audit);
|
|
2683
|
+
const scopes = [];
|
|
2684
|
+
for (const collection of collections) {
|
|
2685
|
+
const scope = await this.buildCollectionAuditScope(c, collection);
|
|
2686
|
+
if (!scope.denied) {
|
|
2687
|
+
scopes.push(scope.where);
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
if (scopes.length === 0) {
|
|
2691
|
+
return c.json(this.emptyResult(query.limit, query.page));
|
|
2692
|
+
}
|
|
2693
|
+
let where = scopes.length === 1 ? scopes[0] : { OR: scopes };
|
|
2694
|
+
if (query.where) {
|
|
2695
|
+
where = mergeWhereConstraint(where, query.where);
|
|
2696
|
+
}
|
|
2697
|
+
const result = await config.db.find({
|
|
2698
|
+
collection: "__audit",
|
|
2699
|
+
where,
|
|
2700
|
+
limit: query.limit,
|
|
2701
|
+
page: query.page,
|
|
2702
|
+
sort: query.sort
|
|
2703
|
+
});
|
|
2704
|
+
return c.json(result);
|
|
2705
|
+
}
|
|
2706
|
+
};
|
|
2707
|
+
|
|
2557
2708
|
// src/middleware/auth.ts
|
|
2558
2709
|
function getBearerToken(c) {
|
|
2559
2710
|
const authHeader = c.req.header("Authorization");
|
|
@@ -2730,6 +2881,7 @@ function serializeFieldForApi(f) {
|
|
|
2730
2881
|
return serialized;
|
|
2731
2882
|
}
|
|
2732
2883
|
function registerRoutes(app, config) {
|
|
2884
|
+
const optionsCache = /* @__PURE__ */ new Map();
|
|
2733
2885
|
app.get("/api/schemas", optionalAuth(config), async (c) => {
|
|
2734
2886
|
const siteId = c.req.header("X-Site-Id");
|
|
2735
2887
|
let collections = [...config.collections];
|
|
@@ -2750,6 +2902,10 @@ function registerRoutes(app, config) {
|
|
|
2750
2902
|
const serializeAccess = async (access) => {
|
|
2751
2903
|
if (typeof access === "string") return access;
|
|
2752
2904
|
if (typeof access === "boolean") return access;
|
|
2905
|
+
if (access && typeof access === "object" && typeof access.policy === "string") {
|
|
2906
|
+
const policy = config.accessPolicies?.[access.policy];
|
|
2907
|
+
if (typeof policy === "string" || typeof policy === "boolean") return policy;
|
|
2908
|
+
}
|
|
2753
2909
|
return resolveBooleanAccess(config, access, accessArgs);
|
|
2754
2910
|
};
|
|
2755
2911
|
const filteredCollections = await Promise.all(collections.filter((col) => !siteId || col.shared || !col.siteId || col.siteId === siteId).map(async (col) => ({
|
|
@@ -2778,6 +2934,7 @@ function registerRoutes(app, config) {
|
|
|
2778
2934
|
admin: f.admin,
|
|
2779
2935
|
access: {
|
|
2780
2936
|
read: await serializeAccess(f.access?.read),
|
|
2937
|
+
create: await serializeAccess(f.access?.create),
|
|
2781
2938
|
update: await serializeAccess(f.access?.update)
|
|
2782
2939
|
}
|
|
2783
2940
|
}))),
|
|
@@ -2885,10 +3042,12 @@ function registerRoutes(app, config) {
|
|
|
2885
3042
|
return c.json({ error: true, message: `Field ${fieldName} not found in ${colSlug}` }, 404);
|
|
2886
3043
|
}
|
|
2887
3044
|
let resolver;
|
|
3045
|
+
let cacheTTL;
|
|
2888
3046
|
if (typeof field.options === "function") {
|
|
2889
3047
|
resolver = field.options;
|
|
2890
3048
|
} else if (field.options && typeof field.options === "object" && "resolve" in field.options) {
|
|
2891
3049
|
resolver = field.options.resolve;
|
|
3050
|
+
cacheTTL = field.options.cacheTTL;
|
|
2892
3051
|
}
|
|
2893
3052
|
if (!resolver) {
|
|
2894
3053
|
return c.json({ error: true, message: `Field ${fieldName} in ${colSlug} is not dynamic` }, 400);
|
|
@@ -2901,11 +3060,32 @@ function registerRoutes(app, config) {
|
|
|
2901
3060
|
headers: c.req.header(),
|
|
2902
3061
|
raw: c.req.raw
|
|
2903
3062
|
};
|
|
3063
|
+
const shouldCache = typeof cacheTTL === "number" && cacheTTL > 0;
|
|
3064
|
+
let cacheKey = "";
|
|
3065
|
+
if (shouldCache) {
|
|
3066
|
+
cacheKey = JSON.stringify([
|
|
3067
|
+
siteId ?? "",
|
|
3068
|
+
colSlug,
|
|
3069
|
+
fieldName,
|
|
3070
|
+
user?.id ?? null,
|
|
3071
|
+
queryParams
|
|
3072
|
+
]);
|
|
3073
|
+
const hit = optionsCache.get(cacheKey);
|
|
3074
|
+
if (hit && hit.expires > Date.now()) {
|
|
3075
|
+
return c.json(hit.value);
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
2904
3078
|
const result = await resolver({
|
|
2905
3079
|
db,
|
|
2906
3080
|
user,
|
|
2907
3081
|
req: reqContext
|
|
2908
3082
|
});
|
|
3083
|
+
if (shouldCache) {
|
|
3084
|
+
optionsCache.set(cacheKey, {
|
|
3085
|
+
expires: Date.now() + cacheTTL * 1e3,
|
|
3086
|
+
value: result
|
|
3087
|
+
});
|
|
3088
|
+
}
|
|
2909
3089
|
return c.json(result);
|
|
2910
3090
|
} catch (err) {
|
|
2911
3091
|
console.error(`[dyrected/core] Failed to resolve dynamic options for field ${fieldName}:`, err);
|
|
@@ -3050,6 +3230,7 @@ function registerRoutes(app, config) {
|
|
|
3050
3230
|
app.post(`${path}/invite`, requireAuth(config), (c) => authController.invite(c));
|
|
3051
3231
|
app.post(`${path}/accept-invite`, (c) => authController.acceptInvite(c));
|
|
3052
3232
|
}
|
|
3233
|
+
const auditController = new AuditController();
|
|
3053
3234
|
for (const collection of config.collections) {
|
|
3054
3235
|
const path = `/api/collections/${collection.slug}`;
|
|
3055
3236
|
const controller = new CollectionController(collection);
|
|
@@ -3057,6 +3238,9 @@ function registerRoutes(app, config) {
|
|
|
3057
3238
|
app.post(path, (c) => controller.create(c));
|
|
3058
3239
|
app.post(`${path}/media`, (c) => controller.create(c));
|
|
3059
3240
|
app.delete(`${path}/delete-many`, (c) => controller.deleteMany(c));
|
|
3241
|
+
if (collection.audit) {
|
|
3242
|
+
app.get(`${path}/__audit`, (c) => auditController.findForCollection(c, collection));
|
|
3243
|
+
}
|
|
3060
3244
|
app.get(`${path}/:id`, (c) => controller.findOne(c));
|
|
3061
3245
|
app.patch(`${path}/:id`, (c) => controller.update(c));
|
|
3062
3246
|
app.delete(`${path}/:id`, (c) => controller.delete(c));
|
|
@@ -3079,6 +3263,24 @@ function registerRoutes(app, config) {
|
|
|
3079
3263
|
const previewController = new PreviewController();
|
|
3080
3264
|
app.post("/api/preview-token", requireAuth(config), (c) => previewController.createToken(c));
|
|
3081
3265
|
app.get("/api/preview-data", (c) => previewController.getData(c));
|
|
3266
|
+
app.get("/api/audit", (c) => auditController.findAll(c));
|
|
3267
|
+
app.get("/api/collections/:slug/__audit", async (c) => {
|
|
3268
|
+
const slug = c.req.param("slug");
|
|
3269
|
+
const siteId = c.req.header("X-Site-Id") || c.get("siteId");
|
|
3270
|
+
const config2 = c.get("config");
|
|
3271
|
+
if (config2.collections.some((col) => col.slug === slug)) {
|
|
3272
|
+
return c.json({ message: "Not Found" }, 404);
|
|
3273
|
+
}
|
|
3274
|
+
if (!config2.onSchemaFetch || !siteId) {
|
|
3275
|
+
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
3276
|
+
}
|
|
3277
|
+
const dynamic = await config2.onSchemaFetch(siteId);
|
|
3278
|
+
const collection = dynamic.collections?.find((col) => col.slug === slug);
|
|
3279
|
+
if (!collection?.audit) {
|
|
3280
|
+
return c.json({ message: `Collection "${slug}" not found or has no audit log` }, 404);
|
|
3281
|
+
}
|
|
3282
|
+
return auditController.findForCollection(c, collection);
|
|
3283
|
+
});
|
|
3082
3284
|
app.post("/api/collections/:slug/:id/transitions/:transition", requireAuth(config), async (c) => {
|
|
3083
3285
|
const slug = c.req.param("slug");
|
|
3084
3286
|
const siteId = c.req.header("X-Site-Id") || c.get("siteId");
|