@classytic/arc 2.28.0 → 2.30.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.
Files changed (83) hide show
  1. package/dist/{BaseController-B65zbz9m.mjs → BaseController-B5DwufbB.mjs} +12 -10
  2. package/dist/EventTransport-C6YA8xY9.mjs +103 -0
  3. package/dist/{HookSystem-C1vB_Gc9.mjs → HookSystem-WkIougo6.mjs} +15 -35
  4. package/dist/applyPermissionResult-Cf6zUCvb.mjs +257 -0
  5. package/dist/audit/index.d.mts +2 -2
  6. package/dist/audit/index.mjs +1 -1
  7. package/dist/auth/index.d.mts +2 -2
  8. package/dist/auth/index.mjs +1 -1
  9. package/dist/{buildHandler-Dtwd8fdz.mjs → buildHandler-BYS3Vkfz.mjs} +32 -6
  10. package/dist/cleanup/index.d.mts +2 -2
  11. package/dist/cleanup/index.mjs +1 -1
  12. package/dist/cli/commands/describe.d.mts +1 -1
  13. package/dist/cli/commands/docs.mjs +1 -1
  14. package/dist/core/index.d.mts +2 -2
  15. package/dist/core/index.mjs +5 -4
  16. package/dist/{core-BGa8TXJR.mjs → core-poalJLvp.mjs} +2 -48
  17. package/dist/{createActionRouter-C-N7z2EL.mjs → createActionRouter-BxuywVRB.mjs} +2 -2
  18. package/dist/{createAggregationRouter-wzwFa_dW.mjs → createAggregationRouter-BiY-lGdY.mjs} +7 -20
  19. package/dist/{createApp-Du5G54TM.mjs → createApp-CXrKFciH.mjs} +24 -3
  20. package/dist/defineAggregation-ufWjUwml.mjs +47 -0
  21. package/dist/{defineResource-2zvVVpxg.mjs → defineResource-CxWqDCAy.mjs} +35 -42
  22. package/dist/docs/index.d.mts +1 -1
  23. package/dist/docs/index.mjs +1 -1
  24. package/dist/{eventPlugin-DrQ_FesK.mjs → eventPlugin-D2NNR4py.mjs} +2 -103
  25. package/dist/events/index.mjs +4 -339
  26. package/dist/factory/index.d.mts +4 -4
  27. package/dist/factory/index.mjs +1 -1
  28. package/dist/{fastify-augmentation-OzYGIiU1.d.mts → fastify-augmentation-0WhPnUH7.d.mts} +3 -3
  29. package/dist/{applyPermissionResult-BnyVE9eD.mjs → fields-CQ_7KQWw.mjs} +1 -107
  30. package/dist/hooks/index.d.mts +1 -1
  31. package/dist/hooks/index.mjs +1 -1
  32. package/dist/idempotency/index.mjs +1 -1
  33. package/dist/{index-CZY4EGga.d.mts → index-CCzVR-N3.d.mts} +1 -1
  34. package/dist/{index-BMnbzvtO.d.mts → index-Ch16aAah.d.mts} +201 -21
  35. package/dist/{index-CjVg-Epg.d.mts → index-Cx7bro4R.d.mts} +4 -4
  36. package/dist/{index-DPRH0Cme.d.mts → index-Q5gzEIWY.d.mts} +97 -66
  37. package/dist/index.d.mts +6 -6
  38. package/dist/index.mjs +8 -7
  39. package/dist/integrations/index.d.mts +1 -1
  40. package/dist/integrations/mcp/index.d.mts +6 -6
  41. package/dist/integrations/mcp/index.mjs +1 -1
  42. package/dist/integrations/mcp/testing.d.mts +1 -1
  43. package/dist/integrations/mcp/testing.mjs +1 -1
  44. package/dist/{mcpPlugin-Cs6nRGCt.mjs → mcpPlugin-nRzm5g2s.mjs} +36 -43
  45. package/dist/middleware/index.d.mts +1 -1
  46. package/dist/migrations/index.d.mts +0 -3
  47. package/dist/migrations/index.mjs +0 -3
  48. package/dist/{openapi-BLV4OMXb.mjs → openapi-B8Vkbxbn.mjs} +1 -1
  49. package/dist/permissions/index.d.mts +3 -3
  50. package/dist/permissions/index.mjs +4 -3
  51. package/dist/{permissions-BrtiBKGs.mjs → permissions-Bfu_8y1U.mjs} +318 -289
  52. package/dist/pipeline/index.d.mts +1 -1
  53. package/dist/{pipeline-BGRnV-To.mjs → pipeline-BJnpkY9i.mjs} +4 -18
  54. package/dist/plugins/index.d.mts +4 -4
  55. package/dist/plugins/index.mjs +2 -2
  56. package/dist/plugins/tracing-entry.mjs +1 -1
  57. package/dist/{preloadResources-CM4uUaFv.d.mts → preloadResources-DCfa2egz.d.mts} +3 -3
  58. package/dist/presets/filesUpload.d.mts +2 -2
  59. package/dist/presets/filesUpload.mjs +1 -1
  60. package/dist/presets/index.d.mts +14 -2
  61. package/dist/presets/index.mjs +1 -1
  62. package/dist/presets/multiTenant.d.mts +1 -1
  63. package/dist/presets/search.d.mts +2 -2
  64. package/dist/presets/search.mjs +1 -1
  65. package/dist/{presets-DwFDVKP0.mjs → presets-CJOEXLuk.mjs} +25 -9
  66. package/dist/projection-UBDgfMwu.mjs +17 -0
  67. package/dist/{realtime-DEuDu9io.mjs → realtime-B9nmIY5V.mjs} +2 -1
  68. package/dist/{registerRoute-DTp18Ah_.mjs → registerRoute-JkxIEBJq.mjs} +3 -1
  69. package/dist/registry/index.d.mts +2 -2
  70. package/dist/registry/index.mjs +1 -1
  71. package/dist/{registry-9km_w1aP.mjs → registry-BYXUUg21.mjs} +1 -1
  72. package/dist/repositoryFilter-Dz8k3S4P.mjs +44 -0
  73. package/dist/subscribe-helpers-DfzsivM6.mjs +339 -0
  74. package/dist/testing/index.d.mts +34 -6
  75. package/dist/testing/index.mjs +314 -211
  76. package/dist/types/index.d.mts +3 -3
  77. package/dist/{types-QFGc3Cjv.d.mts → types-Bmmn-fO0.d.mts} +1 -1
  78. package/dist/{types-Dmw-6Qeo.d.mts → types-ChvvVNP8.d.mts} +93 -88
  79. package/dist/{types-Cu6vVJmh.d.mts → types-D_n8L6VY.d.mts} +32 -1
  80. package/dist/usage/index.d.mts +1 -1
  81. package/dist/usage/index.mjs +0 -0
  82. package/dist/utils/index.d.mts +2 -5
  83. package/package.json +539 -539
@@ -1,4 +1,4 @@
1
- import { Ct as PipelineStep, Rt as IControllerResponse, St as PipelineContext, _t as Guard, bt as OperationFilter, vt as Interceptor, wt as Transform, xt as PipelineConfig, yt as NextFunction } from "../index-DPRH0Cme.mjs";
1
+ import { Ct as PipelineStep, Rt as IControllerResponse, St as PipelineContext, _t as Guard, bt as OperationFilter, vt as Interceptor, wt as Transform, xt as PipelineConfig, yt as NextFunction } from "../index-Q5gzEIWY.mjs";
2
2
  //#region src/pipeline/guard.d.ts
3
3
  interface GuardOptions {
4
4
  operations?: OperationFilter;
@@ -1,23 +1,9 @@
1
1
  import { n as getUserRoles } from "./userHelpers-Cg_y3fBz.mjs";
2
- import { b as isElevated, c as getOrgId, g as getUserId, n as PUBLIC_SCOPE, x as isMember } from "./types-CSyCxl2V.mjs";
3
- import { i as applyFieldReadPermissions, s as resolveEffectiveRoles } from "./applyPermissionResult-BnyVE9eD.mjs";
2
+ import { b as isElevated, n as PUBLIC_SCOPE, x as isMember } from "./types-CSyCxl2V.mjs";
3
+ import { i as resolveEffectiveRoles, t as applyFieldReadPermissions } from "./fields-CQ_7KQWw.mjs";
4
4
  import { t as executePipeline } from "./pipe-BOLmGIxa.mjs";
5
+ import { t as buildRequestScopeProjection } from "./projection-UBDgfMwu.mjs";
5
6
  import { isPaginatedResult, toCanonicalList } from "@classytic/repo-core/pagination";
6
- //#region src/scope/projection.ts
7
- /**
8
- * Compute the request-scope projection. Returns `undefined` when no
9
- * scope is attached (public / unscoped routes) so hosts can idiomatically
10
- * write `ctx.scope?.organizationId` without a double-null check.
11
- */
12
- function buildRequestScopeProjection(scope) {
13
- if (!scope) return void 0;
14
- return {
15
- organizationId: getOrgId(scope),
16
- userId: getUserId(scope),
17
- orgRoles: isMember(scope) ? scope.orgRoles : void 0
18
- };
19
- }
20
- //#endregion
21
7
  //#region src/core/fastifyAdapter.ts
22
8
  /** Type guard for Mongoose-like documents with toObject() */
23
9
  function isMongooseDoc(obj) {
@@ -334,4 +320,4 @@ function buildActionPipelineHandler(handler, steps, operation, resourceName) {
334
320
  };
335
321
  }
336
322
  //#endregion
337
- export { createFastifyHandler as a, getControllerScope as c, createCrudHandlers as i, sendControllerResponse as l, buildPipelineHandler as n, createRequestContext as o, resolvePipelineSteps as r, getControllerContext as s, buildActionPipelineHandler as t, buildRequestScopeProjection as u };
323
+ export { createFastifyHandler as a, getControllerScope as c, createCrudHandlers as i, sendControllerResponse as l, buildPipelineHandler as n, createRequestContext as o, resolvePipelineSteps as r, getControllerContext as s, buildActionPipelineHandler as t };
@@ -1,8 +1,8 @@
1
- import { At as RouteDefinition, J as PresetHook, Q as MiddlewareConfig, et as RouteSchemaOptions } from "../index-DPRH0Cme.mjs";
2
- import { c as AnyRecord } from "../types-Dmw-6Qeo.mjs";
1
+ import { At as RouteDefinition, J as PresetHook, Q as MiddlewareConfig, et as RouteSchemaOptions } from "../index-Q5gzEIWY.mjs";
2
+ import { l as AnyRecord } from "../types-ChvvVNP8.mjs";
3
3
  import { i as errorHandlerPlugin, n as ErrorMapper, r as defaultIsDuplicateKeyError, t as ErrorHandlerOptions } from "../errorHandler-C6yv4ZTW.mjs";
4
- import { _ as _default$3, d as ScheduleStats, f as SchedulesPluginOptions, g as HealthOptions, h as HealthCheck, m as schedulesPlugin, p as _default$7, u as ScheduleDefinition, v as healthPlugin } from "../types-Cu6vVJmh.mjs";
5
- import { a as arcCorePlugin, i as _default, n as ArcCorePluginOptions, r as PluginMeta, t as ArcCore } from "../fastify-augmentation-OzYGIiU1.mjs";
4
+ import { _ as _default$3, d as ScheduleStats, f as SchedulesPluginOptions, g as HealthOptions, h as HealthCheck, m as schedulesPlugin, p as _default$7, u as ScheduleDefinition, v as healthPlugin } from "../types-D_n8L6VY.mjs";
5
+ import { a as arcCorePlugin, i as _default, n as ArcCorePluginOptions, r as PluginMeta, t as ArcCore } from "../fastify-augmentation-0WhPnUH7.mjs";
6
6
  import { _ as CachingRule, a as MetricsCollector, c as metricsPlugin, d as _default$5, f as realtimePlugin, g as CachingOptions, h as ssePlugin, i as MetricEntry, l as ChangeOperation, m as _default$8, n as _default$9, o as MetricsOptions, p as SSEOptions, r as versioningPlugin, s as _default$4, t as VersioningOptions, u as RealtimeOptions, v as _default$1, y as cachingPlugin } from "../versioning-DXgY5BUm.mjs";
7
7
  import { t as TracingOptions } from "../tracing-CsGKQJGg.mjs";
8
8
  import { PaginatedResult } from "@classytic/repo-core/pagination";
@@ -2,13 +2,13 @@ import { p as MUTATION_OPERATIONS } from "../constants-DNre0wRg.mjs";
2
2
  import { c as getOrgId } from "../types-CSyCxl2V.mjs";
3
3
  import { n as requestContext } from "../requestContext-MrlJwECf.mjs";
4
4
  import { t as hasEvents } from "../typeGuards-C9KXUly6.mjs";
5
- import { t as HookSystem } from "../HookSystem-C1vB_Gc9.mjs";
5
+ import { t as HookSystem } from "../HookSystem-WkIougo6.mjs";
6
6
  import { t as ResourceRegistry } from "../ResourceRegistry-y9Hnypus.mjs";
7
7
  import { a as requestIdPlugin, i as createRequestIdGenerator, n as health_default, o as requestId_default, s as sanitizeRequestId, t as healthPlugin } from "../health-CB_m4S32.mjs";
8
8
  import { n as caching_default, t as cachingPlugin } from "../caching-_NJeDd1_.mjs";
9
9
  import { n as errorHandlerPlugin, t as defaultIsDuplicateKeyError } from "../errorHandler-Bq2OeEQL.mjs";
10
10
  import { n as metrics_default, t as metricsPlugin } from "../metrics-fdxI5mn_.mjs";
11
- import { n as realtime_default, t as realtimePlugin } from "../realtime-DEuDu9io.mjs";
11
+ import { n as realtime_default, t as realtimePlugin } from "../realtime-B9nmIY5V.mjs";
12
12
  import { t as replyHelpersPlugin } from "../replyHelpers-67tRawQZ.mjs";
13
13
  import { n as schedules_default, t as schedulesPlugin } from "../schedules-CjOd2MG9.mjs";
14
14
  import { n as sse_default, t as ssePlugin } from "../sse-BQ1w0dfl.mjs";
@@ -58,7 +58,7 @@ try {
58
58
  function createTracerProvider(options) {
59
59
  if (!isAvailable || !NodeTracerProvider || !BatchSpanProcessor || !OTLPTraceExporter) return null;
60
60
  const { serviceName = "@classytic/arc", serviceVersion, exporterUrl = "http://localhost:4318/v1/traces" } = options;
61
- const resolvedVersion = serviceVersion ?? "2.28.0";
61
+ const resolvedVersion = serviceVersion ?? "2.30.0";
62
62
  const exporter = new OTLPTraceExporter({ url: exporterUrl });
63
63
  const provider = new NodeTracerProvider({ resource: { attributes: {
64
64
  "service.name": serviceName,
@@ -1,12 +1,12 @@
1
- import { Wt as Authenticator } from "./index-DPRH0Cme.mjs";
1
+ import { Wt as Authenticator } from "./index-Q5gzEIWY.mjs";
2
2
  import { r as CacheStore } from "./interface-BGZCzOGL.mjs";
3
3
  import { n as ElevationOptions } from "./elevation-C9s5hFP_.mjs";
4
4
  import { a as EventTransport } from "./EventTransport-QmY7A258.mjs";
5
5
  import { t as ExternalOpenApiPaths } from "./externalPaths-BE6wFpYv.mjs";
6
6
  import { r as QueryCachePluginOptions } from "./queryCachePlugin-br4otiqF.mjs";
7
7
  import { t as ErrorHandlerOptions } from "./errorHandler-C6yv4ZTW.mjs";
8
- import { f as SchedulesPluginOptions, g as HealthOptions, n as ArcModuleInput, s as ResourceLike } from "./types-Cu6vVJmh.mjs";
9
- import "./index-CZY4EGga.mjs";
8
+ import { f as SchedulesPluginOptions, g as HealthOptions, n as ArcModuleInput, s as ResourceLike } from "./types-D_n8L6VY.mjs";
9
+ import "./index-CCzVR-N3.mjs";
10
10
  import { t as EventPluginOptions } from "./eventPlugin-Bg3cP2_u.mjs";
11
11
  import { g as CachingOptions, o as MetricsOptions, p as SSEOptions, t as VersioningOptions, u as RealtimeOptions } from "./versioning-DXgY5BUm.mjs";
12
12
  import { r as IdempotencyStore } from "./interface-Uk8ONJv7.mjs";
@@ -1,6 +1,6 @@
1
- import { Y as PresetResult } from "../index-DPRH0Cme.mjs";
1
+ import { Y as PresetResult } from "../index-Q5gzEIWY.mjs";
2
2
  import { i as RequestScope } from "../types-DjC-KUCz.mjs";
3
- import { t as PermissionCheck } from "../types-Dmw-6Qeo.mjs";
3
+ import { r as PermissionCheck } from "../types-ChvvVNP8.mjs";
4
4
  import { a as StorageReadResult, i as StorageReadRange, n as StorageContext, o as StorageUploadInput, r as StorageFile, t as Storage } from "../storage-DLUZ04kK.mjs";
5
5
  //#region src/presets/filesUpload.d.ts
6
6
  interface FilesUploadPresetRoutes {
@@ -1,6 +1,6 @@
1
1
  import { i as NotFoundError, u as ValidationError } from "../errors-DkO8Nk6-.mjs";
2
2
  import { c as getOrgId, g as getUserId } from "../types-CSyCxl2V.mjs";
3
- import { A as requireAuth, E as allowPublic } from "../permissions-BrtiBKGs.mjs";
3
+ import { I as requireAuth, j as allowPublic } from "../permissions-Bfu_8y1U.mjs";
4
4
  import { t as multipartBody } from "../multipartBody-D0eBvkeq.mjs";
5
5
  //#region src/presets/filesUpload.ts
6
6
  const DEFAULT_FIELD_NAME = "file";
@@ -1,11 +1,23 @@
1
- import { Rt as IControllerResponse, U as ResourceConfig, Y as PresetResult, d as PaginationResult, zt as IRequestContext } from "../index-DPRH0Cme.mjs";
2
- import { c as AnyRecord } from "../types-Dmw-6Qeo.mjs";
1
+ import { Rt as IControllerResponse, U as ResourceConfig, Y as PresetResult, d as PaginationResult, zt as IRequestContext } from "../index-Q5gzEIWY.mjs";
2
+ import { l as AnyRecord } from "../types-ChvvVNP8.mjs";
3
3
  import { FilesUploadPresetOptions, FilesUploadPresetPermissions, FilesUploadPresetRoutes, filesUploadPreset } from "./filesUpload.mjs";
4
4
  import { MultiTenantOptions, TenantFieldSpec, multiTenantPreset } from "./multiTenant.mjs";
5
5
  import { SearchHandler, SearchPresetOptions, SearchRouteConfig, searchPreset } from "./search.mjs";
6
6
  //#region src/presets/ownedByUser.d.ts
7
7
  interface OwnedByUserOptions {
8
8
  ownerField?: string;
9
+ /**
10
+ * What to do when a target record has NO value in `ownerField` (legacy /
11
+ * unowned records).
12
+ *
13
+ * - `"deny"` (default): fail closed — an unowned record is NOT modifiable
14
+ * through an ownership-gated route. This is the secure default; ownership
15
+ * that silently passes for records missing the owner is not an ownership
16
+ * primitive.
17
+ * - `"allow"`: legacy compatibility — permit modifying records that predate
18
+ * the owner field. Visibly unsafe; opt in only for a bounded migration.
19
+ */
20
+ missingOwner?: "deny" | "allow";
9
21
  }
10
22
  declare function ownedByUserPreset(options?: OwnedByUserOptions): PresetResult;
11
23
  //#endregion
@@ -1,5 +1,5 @@
1
1
  import { multiTenantPreset } from "./multiTenant.mjs";
2
- import { a as registerPreset, c as auditedPreset, d as ownedByUserPreset, i as getPreset, l as softDeletePreset, n as flexibleMultiTenantPreset, o as treePreset, r as getAvailablePresets, s as bulkPreset, t as applyPresets, u as slugLookupPreset } from "../presets-DwFDVKP0.mjs";
2
+ import { a as registerPreset, c as auditedPreset, d as ownedByUserPreset, i as getPreset, l as softDeletePreset, n as flexibleMultiTenantPreset, o as treePreset, r as getAvailablePresets, s as bulkPreset, t as applyPresets, u as slugLookupPreset } from "../presets-CJOEXLuk.mjs";
3
3
  import { filesUploadPreset } from "./filesUpload.mjs";
4
4
  import { searchPreset } from "./search.mjs";
5
5
  export { applyPresets, auditedPreset, bulkPreset, filesUploadPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, searchPreset, slugLookupPreset, softDeletePreset, treePreset };
@@ -1,4 +1,4 @@
1
- import { X as CrudRouteKey, Y as PresetResult } from "../index-DPRH0Cme.mjs";
1
+ import { X as CrudRouteKey, Y as PresetResult } from "../index-Q5gzEIWY.mjs";
2
2
  //#region src/presets/multiTenant.d.ts
3
3
  /**
4
4
  * One tenant dimension for multi-field filtering. Discriminated by source:
@@ -1,5 +1,5 @@
1
- import { At as RouteDefinition, Pt as ControllerHandler, Y as PresetResult, jt as RouteMcpConfig } from "../index-DPRH0Cme.mjs";
2
- import { t as PermissionCheck } from "../types-Dmw-6Qeo.mjs";
1
+ import { At as RouteDefinition, Pt as ControllerHandler, Y as PresetResult, jt as RouteMcpConfig } from "../index-Q5gzEIWY.mjs";
2
+ import { r as PermissionCheck } from "../types-ChvvVNP8.mjs";
3
3
  //#region src/presets/search.d.ts
4
4
  /**
5
5
  * Search Preset — backend-agnostic search / vector / embed routes
@@ -1,4 +1,4 @@
1
- import { A as requireAuth, E as allowPublic } from "../permissions-BrtiBKGs.mjs";
1
+ import { I as requireAuth, j as allowPublic } from "../permissions-Bfu_8y1U.mjs";
2
2
  //#region src/presets/search.ts
3
3
  const BUILTINS = [
4
4
  {
@@ -1,29 +1,40 @@
1
+ import { l as UnauthorizedError } from "./errors-DkO8Nk6-.mjs";
1
2
  import { b as isElevated, n as PUBLIC_SCOPE } from "./types-CSyCxl2V.mjs";
2
- import { A as requireAuth, E as allowPublic, M as requireRoles } from "./permissions-BrtiBKGs.mjs";
3
+ import { I as requireAuth, R as requireRoles, j as allowPublic } from "./permissions-Bfu_8y1U.mjs";
3
4
  import { multiTenantPreset } from "./presets/multiTenant.mjs";
4
5
  //#region src/presets/ownedByUser.ts
5
6
  /**
6
7
  * Create ownership check middleware.
7
- * Elevated scope (platform admin) bypasses ownership checks.
8
+ *
9
+ * FAIL-CLOSED: ownership is meaningless without an authenticated identity, so a
10
+ * request with no user (or a user with no id) is DENIED here rather than sliding
11
+ * through with no ownership check installed. Elevated scope (platform admin)
12
+ * bypasses the check by design.
8
13
  */
9
- function createOwnershipCheck(ownerField) {
14
+ function createOwnershipCheck(ownerField, missingOwner) {
10
15
  return async (request, _reply) => {
11
- const user = request.user;
12
- if (!user) return;
13
16
  if (isElevated(request.scope ?? PUBLIC_SCOPE)) return;
17
+ const user = request.user;
18
+ if (!user) throw new UnauthorizedError("Authentication required for this operation");
14
19
  const userWithId = user;
15
20
  const userId = userWithId._id ?? userWithId.id;
16
- if (userId) request._ownershipCheck = {
21
+ if (!userId) throw new UnauthorizedError("Authenticated identity has no usable id");
22
+ request._ownershipCheck = {
17
23
  field: ownerField,
18
- userId
24
+ userId,
25
+ missingOwner
19
26
  };
20
27
  };
21
28
  }
22
29
  function ownedByUserPreset(options = {}) {
23
- const { ownerField = "userId" } = options;
24
- const ownershipMiddleware = createOwnershipCheck(ownerField);
30
+ const { ownerField = "userId", missingOwner = "deny" } = options;
31
+ const ownershipMiddleware = createOwnershipCheck(ownerField, missingOwner);
25
32
  return {
26
33
  name: "ownedByUser",
34
+ permissions: {
35
+ update: requireAuth(),
36
+ delete: requireAuth()
37
+ },
27
38
  middlewares: {
28
39
  update: [ownershipMiddleware],
29
40
  delete: [ownershipMiddleware]
@@ -366,6 +377,11 @@ function mergePreset(config, preset) {
366
377
  result.middlewares[key] = [...result.middlewares[key] ?? [], ...mws ?? []];
367
378
  }
368
379
  }
380
+ if (preset.permissions) {
381
+ const merged = { ...result.permissions };
382
+ for (const [op, check] of Object.entries(preset.permissions)) if (merged[op] === void 0 && check !== void 0) merged[op] = check;
383
+ result.permissions = merged;
384
+ }
369
385
  if (preset.schemaOptions) result.schemaOptions = {
370
386
  ...result.schemaOptions,
371
387
  ...preset.schemaOptions,
@@ -0,0 +1,17 @@
1
+ import { c as getOrgId, g as getUserId, x as isMember } from "./types-CSyCxl2V.mjs";
2
+ //#region src/scope/projection.ts
3
+ /**
4
+ * Compute the request-scope projection. Returns `undefined` when no
5
+ * scope is attached (public / unscoped routes) so hosts can idiomatically
6
+ * write `ctx.scope?.organizationId` without a double-null check.
7
+ */
8
+ function buildRequestScopeProjection(scope) {
9
+ if (!scope) return void 0;
10
+ return {
11
+ organizationId: getOrgId(scope),
12
+ userId: getUserId(scope),
13
+ orgRoles: isMember(scope) ? scope.orgRoles : void 0
14
+ };
15
+ }
16
+ //#endregion
17
+ export { buildRequestScopeProjection as t };
@@ -3,7 +3,8 @@ import { arcLog } from "./logger/index.mjs";
3
3
  import { n as getUserRoles } from "./userHelpers-Cg_y3fBz.mjs";
4
4
  import { c as getOrgId } from "./types-CSyCxl2V.mjs";
5
5
  import { t as simpleEqualityMatcher } from "./simpleEqualityMatcher-Cg05HPsn.mjs";
6
- import { i as applyFieldReadPermissions, n as evaluateAndApplyPermission } from "./applyPermissionResult-BnyVE9eD.mjs";
6
+ import { r as evaluateAndApplyPermission } from "./applyPermissionResult-Cf6zUCvb.mjs";
7
+ import { t as applyFieldReadPermissions } from "./fields-CQ_7KQWw.mjs";
7
8
  import { a as promoteStreamTokenToHeader } from "./streaming-BQcW6_Kq.mjs";
8
9
  import { t as openSseStream } from "./sseStream-BQ7Qaynp.mjs";
9
10
  import fp from "fastify-plugin";
@@ -1,5 +1,6 @@
1
1
  import { f as createError } from "./errors-DkO8Nk6-.mjs";
2
- import { n as evaluateAndApplyPermission } from "./applyPermissionResult-BnyVE9eD.mjs";
2
+ import { u as getRequestScope } from "./types-CSyCxl2V.mjs";
3
+ import { r as evaluateAndApplyPermission } from "./applyPermissionResult-Cf6zUCvb.mjs";
3
4
  //#region src/core/middlewares/permissions.ts
4
5
  /**
5
6
  * Build a PermissionContext from a Fastify request. Extracted so the CRUD
@@ -11,6 +12,7 @@ function buildPermissionContext(req, opts) {
11
12
  const params = req.params;
12
13
  return {
13
14
  user: reqWithExtras.user ?? null,
15
+ scope: getRequestScope(req),
14
16
  request: req,
15
17
  resource: opts.resource,
16
18
  action: opts.action,
@@ -1,5 +1,5 @@
1
- import { K as ResolvedTenantPurge, R as RegisterOptions, V as ResourceDefinition, X as CrudRouteKey, k as IntrospectionPluginOptions, p as RegistryEntry, z as ResourceRegistry } from "../index-DPRH0Cme.mjs";
2
- import "../fastify-augmentation-OzYGIiU1.mjs";
1
+ import { K as ResolvedTenantPurge, R as RegisterOptions, V as ResourceDefinition, X as CrudRouteKey, k as IntrospectionPluginOptions, p as RegistryEntry, z as ResourceRegistry } from "../index-Q5gzEIWY.mjs";
2
+ import "../fastify-augmentation-0WhPnUH7.mjs";
3
3
  import { FastifyPluginAsync } from "fastify";
4
4
  import { TenantPurgeProgress, TenantPurgeStrategy } from "@classytic/repo-core/repository";
5
5
  //#region src/registry/assertNoTenantData.d.ts
@@ -1,3 +1,3 @@
1
- import { a as cascadeDeleteForOrganization, c as assertNoTenantData, i as introspectionPlugin_default, n as buildResourceManifestFromRegistry, o as getCascadingResources, r as introspectionPlugin, s as getCascadingResourcesWithMetadata, t as buildResourceManifest } from "../registry-9km_w1aP.mjs";
1
+ import { a as cascadeDeleteForOrganization, c as assertNoTenantData, i as introspectionPlugin_default, n as buildResourceManifestFromRegistry, o as getCascadingResources, r as introspectionPlugin, s as getCascadingResourcesWithMetadata, t as buildResourceManifest } from "../registry-BYXUUg21.mjs";
2
2
  import { t as ResourceRegistry } from "../ResourceRegistry-y9Hnypus.mjs";
3
3
  export { ResourceRegistry, assertNoTenantData, buildResourceManifest, buildResourceManifestFromRegistry, cascadeDeleteForOrganization, getCascadingResources, getCascadingResourcesWithMetadata, introspectionPlugin_default as introspectionPlugin, introspectionPlugin as introspectionPluginFn };
@@ -440,7 +440,7 @@ var introspectionPlugin_default = fp(introspectionPlugin, { name: "arc-introspec
440
440
  //#endregion
441
441
  //#region src/registry/manifest.ts
442
442
  /**
443
- * Resource manifest for frontend code generators (v2.15.5).
443
+ * Resource manifest for frontend code generators.
444
444
  *
445
445
  * Every host that hand-rolls a `createCrudApi('foo')` helper on the frontend
446
446
  * ends up rewriting the same shim for declarative actions: `postAction(id,
@@ -0,0 +1,44 @@
1
+ import { arcLog } from "./logger/index.mjs";
2
+ import { policyRecordToFilter } from "@classytic/repo-core/filter";
3
+ //#region src/core/repositoryFilter.ts
4
+ const log = arcLog("repository-filter");
5
+ let warnedUnconvertible = false;
6
+ /** True for a `{ $op: ... }` operator object (all keys `$`-prefixed). */
7
+ function isOperatorObject(value) {
8
+ if (value === null || typeof value !== "object") return false;
9
+ if (Array.isArray(value) || value instanceof Date) return false;
10
+ const keys = Object.keys(value);
11
+ return keys.length > 0 && keys.some((k) => k.startsWith("$"));
12
+ }
13
+ /**
14
+ * Does this compound filter contain any `$`-operator (top-level logical like
15
+ * `$and`/`$or`, or a field operator object like `{ $in: [...] }`)? If not, it is
16
+ * pure flat equality and portable as-is.
17
+ */
18
+ function filterHasDollarOperator(record) {
19
+ for (const [key, value] of Object.entries(record)) {
20
+ if (key.startsWith("$")) return true;
21
+ if (isOperatorObject(value)) return true;
22
+ }
23
+ return false;
24
+ }
25
+ /**
26
+ * Normalize a compound query/policy filter for the repository layer. Flat
27
+ * equality records pass through unchanged; anything carrying `$`-operators is
28
+ * converted to the portable repo-core {@link Filter} IR so it compiles
29
+ * identically on Mongo, SQLite, Postgres, and custom adapters.
30
+ */
31
+ function toRepositoryFilter(record) {
32
+ if (!filterHasDollarOperator(record)) return record;
33
+ try {
34
+ return policyRecordToFilter(record);
35
+ } catch (err) {
36
+ if (!warnedUnconvertible) {
37
+ warnedUnconvertible = true;
38
+ log.warn(`A \`\$\`-operator filter could not be normalized to portable Filter IR (operator outside arc's policy dialect). Passing the raw record to the repository — MongoKit handles it; non-Mongo kits may not. (${err instanceof Error ? err.message : String(err)})`);
39
+ }
40
+ return record;
41
+ }
42
+ }
43
+ //#endregion
44
+ export { toRepositoryFilter as t };