@classytic/arc 2.25.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 (92) hide show
  1. package/README.md +2 -0
  2. package/dist/{BaseController--M8EMzt0.mjs → BaseController-B5DwufbB.mjs} +15 -11
  3. package/dist/EventTransport-C6YA8xY9.mjs +103 -0
  4. package/dist/{HookSystem-C1vB_Gc9.mjs → HookSystem-WkIougo6.mjs} +15 -35
  5. package/dist/applyPermissionResult-Cf6zUCvb.mjs +257 -0
  6. package/dist/audit/index.d.mts +2 -2
  7. package/dist/audit/index.mjs +1 -1
  8. package/dist/auth/index.d.mts +2 -2
  9. package/dist/auth/index.mjs +1 -1
  10. package/dist/{buildHandler-Dtwd8fdz.mjs → buildHandler-BYS3Vkfz.mjs} +32 -6
  11. package/dist/cleanup/index.d.mts +313 -0
  12. package/dist/cleanup/index.mjs +1267 -0
  13. package/dist/cli/commands/describe.d.mts +1 -1
  14. package/dist/cli/commands/docs.mjs +1 -1
  15. package/dist/core/index.d.mts +2 -2
  16. package/dist/core/index.mjs +5 -3
  17. package/dist/core-poalJLvp.mjs +175 -0
  18. package/dist/{createActionRouter-B6GvAuAX.mjs → createActionRouter-BxuywVRB.mjs} +2 -2
  19. package/dist/{createAggregationRouter-wzwFa_dW.mjs → createAggregationRouter-BiY-lGdY.mjs} +7 -20
  20. package/dist/{preloadResources-DfyngIN9.mjs → createApp-CXrKFciH.mjs} +25 -76
  21. package/dist/defineAggregation-ufWjUwml.mjs +47 -0
  22. package/dist/{core-EUNu0u07.mjs → defineResource-CxWqDCAy.mjs} +36 -261
  23. package/dist/docs/index.d.mts +1 -1
  24. package/dist/docs/index.mjs +1 -1
  25. package/dist/{eventPlugin-DrQ_FesK.mjs → eventPlugin-D2NNR4py.mjs} +2 -103
  26. package/dist/events/index.d.mts +28 -2
  27. package/dist/events/index.mjs +24 -343
  28. package/dist/factory/index.d.mts +4 -3
  29. package/dist/factory/index.mjs +3 -1
  30. package/dist/{fastify-augmentation-dPNBVeIj.d.mts → fastify-augmentation-0WhPnUH7.d.mts} +3 -2
  31. package/dist/{applyPermissionResult-Tw4iXtlf.mjs → fields-CQ_7KQWw.mjs} +1 -97
  32. package/dist/hooks/index.d.mts +1 -1
  33. package/dist/hooks/index.mjs +1 -1
  34. package/dist/idempotency/index.mjs +1 -1
  35. package/dist/index-CCzVR-N3.d.mts +107 -0
  36. package/dist/{index-BMnbzvtO.d.mts → index-Ch16aAah.d.mts} +201 -21
  37. package/dist/{index-f4PTrk3B.d.mts → index-Cx7bro4R.d.mts} +4 -4
  38. package/dist/{index-DPRH0Cme.d.mts → index-Q5gzEIWY.d.mts} +97 -66
  39. package/dist/index.d.mts +6 -6
  40. package/dist/index.mjs +8 -6
  41. package/dist/integrations/index.d.mts +1 -1
  42. package/dist/integrations/mcp/index.d.mts +6 -6
  43. package/dist/integrations/mcp/index.mjs +1 -1
  44. package/dist/integrations/mcp/testing.d.mts +1 -1
  45. package/dist/integrations/mcp/testing.mjs +1 -1
  46. package/dist/{mcpPlugin-Cb7olw0j.mjs → mcpPlugin-nRzm5g2s.mjs} +36 -43
  47. package/dist/middleware/index.d.mts +1 -1
  48. package/dist/migrations/index.d.mts +0 -3
  49. package/dist/migrations/index.mjs +0 -3
  50. package/dist/module-_-M7C5ES.mjs +29 -0
  51. package/dist/{openapi-DrLtog_B.mjs → openapi-B8Vkbxbn.mjs} +1 -1
  52. package/dist/permissions/index.d.mts +3 -3
  53. package/dist/permissions/index.mjs +4 -3
  54. package/dist/{permissions-BrtiBKGs.mjs → permissions-Bfu_8y1U.mjs} +318 -289
  55. package/dist/pipeline/index.d.mts +1 -1
  56. package/dist/{pipeline-BN5G05rd.mjs → pipeline-BJnpkY9i.mjs} +4 -18
  57. package/dist/plugins/index.d.mts +4 -4
  58. package/dist/plugins/index.mjs +2 -2
  59. package/dist/plugins/tracing-entry.mjs +1 -1
  60. package/dist/{preloadResources-0oe-JSSS.d.mts → preloadResources-DCfa2egz.d.mts} +3 -2
  61. package/dist/preloadResources-DSY5HGpS.mjs +45 -0
  62. package/dist/presets/filesUpload.d.mts +2 -2
  63. package/dist/presets/filesUpload.mjs +1 -1
  64. package/dist/presets/index.d.mts +14 -2
  65. package/dist/presets/index.mjs +1 -1
  66. package/dist/presets/multiTenant.d.mts +1 -1
  67. package/dist/presets/search.d.mts +2 -2
  68. package/dist/presets/search.mjs +1 -1
  69. package/dist/{presets-DwFDVKP0.mjs → presets-CJOEXLuk.mjs} +25 -9
  70. package/dist/projection-UBDgfMwu.mjs +17 -0
  71. package/dist/{realtime-Cuw_deA-.mjs → realtime-B9nmIY5V.mjs} +2 -1
  72. package/dist/{registerRoute-v0Wb6Plt.mjs → registerRoute-JkxIEBJq.mjs} +3 -1
  73. package/dist/registry/index.d.mts +2 -2
  74. package/dist/registry/index.mjs +1 -1
  75. package/dist/{registry-vcOCtPij.mjs → registry-BYXUUg21.mjs} +16 -1
  76. package/dist/repositoryFilter-Dz8k3S4P.mjs +44 -0
  77. package/dist/subscribe-helpers-DfzsivM6.mjs +339 -0
  78. package/dist/testing/cleanupStoreContract.d.mts +10 -0
  79. package/dist/testing/cleanupStoreContract.mjs +375 -0
  80. package/dist/testing/index.d.mts +34 -5
  81. package/dist/testing/index.mjs +316 -211
  82. package/dist/testing/outboxStoreContract.d.mts +13 -0
  83. package/dist/testing/outboxStoreContract.mjs +247 -0
  84. package/dist/types/index.d.mts +3 -3
  85. package/dist/{types-QFGc3Cjv.d.mts → types-Bmmn-fO0.d.mts} +1 -1
  86. package/dist/types-C_ZZRcD6.d.mts +450 -0
  87. package/dist/{types-Dmw-6Qeo.d.mts → types-ChvvVNP8.d.mts} +93 -88
  88. package/dist/{index-KUoxjIq1.d.mts → types-D_n8L6VY.d.mts} +33 -106
  89. package/dist/usage/index.d.mts +1 -1
  90. package/dist/usage/index.mjs +0 -0
  91. package/dist/utils/index.d.mts +2 -5
  92. package/package.json +539 -524
@@ -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-Tw4iXtlf.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 { C as schedulesPlugin, D as healthPlugin, E as _default$3, S as _default$7, T as HealthOptions, b as ScheduleStats, w as HealthCheck, x as SchedulesPluginOptions, y as ScheduleDefinition } from "../index-KUoxjIq1.mjs";
5
- import { a as arcCorePlugin, i as _default, n as ArcCorePluginOptions, r as PluginMeta, t as ArcCore } from "../fastify-augmentation-dPNBVeIj.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-Cuw_deA-.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.25.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,11 +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 { T as HealthOptions, d as ArcModuleInput, g as ResourceLike, x as SchedulesPluginOptions } from "./index-KUoxjIq1.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";
9
10
  import { t as EventPluginOptions } from "./eventPlugin-Bg3cP2_u.mjs";
10
11
  import { g as CachingOptions, o as MetricsOptions, p as SSEOptions, t as VersioningOptions, u as RealtimeOptions } from "./versioning-DXgY5BUm.mjs";
11
12
  import { r as IdempotencyStore } from "./interface-Uk8ONJv7.mjs";
@@ -0,0 +1,45 @@
1
+ //#region src/factory/preloadResources.ts
2
+ /**
3
+ * Normalize an eager `import.meta.glob` result into a `ResourceLike[]`.
4
+ *
5
+ * Accepts either:
6
+ * - `{ import: 'default' }` form: values are the resource directly
7
+ * - default form: values are the full module — picks first export with `toPlugin()`
8
+ *
9
+ * Throws if any module doesn't yield a valid `ResourceLike`.
10
+ */
11
+ function preloadResources(globResult) {
12
+ const resources = [];
13
+ for (const [path, value] of Object.entries(globResult)) {
14
+ const resource = pickResource(value);
15
+ if (!resource) throw new Error(`preloadResources: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
16
+ resources.push(resource);
17
+ }
18
+ return resources.sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
19
+ }
20
+ /**
21
+ * Normalize a lazy `import.meta.glob` result into a `Promise<ResourceLike[]>`.
22
+ *
23
+ * Use this when resources depend on prior bootstrap (e.g., engine init) and
24
+ * cannot be evaluated at import time of the preload file.
25
+ */
26
+ async function preloadResourcesAsync(globResult) {
27
+ return (await Promise.all(Object.entries(globResult).map(async ([path, loader]) => {
28
+ const resource = pickResource(await loader());
29
+ if (!resource) throw new Error(`preloadResourcesAsync: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
30
+ return resource;
31
+ }))).sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
32
+ }
33
+ function pickResource(value) {
34
+ if (!value || typeof value !== "object") return void 0;
35
+ if (typeof value.toPlugin === "function") return value;
36
+ const mod = value;
37
+ const candidates = [
38
+ mod.default,
39
+ mod.resource,
40
+ ...Object.values(mod)
41
+ ];
42
+ for (const c of candidates) if (c && typeof c === "object" && typeof c.toPlugin === "function") return c;
43
+ }
44
+ //#endregion
45
+ export { preloadResourcesAsync as n, preloadResources as t };
@@ -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-Tw4iXtlf.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-Tw4iXtlf.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-dPNBVeIj.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-vcOCtPij.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 };
@@ -79,6 +79,9 @@ async function assertNoTenantData(registry, options) {
79
79
  *
80
80
  * - `skip` strategy: returns `path: 'skipped'` without touching the repo.
81
81
  * - `purgeByField` present: routes through it (chunked, progress, abort).
82
+ * - `purgeByField` absent + `requireChunked`: returns `path: 'unsupported'`
83
+ * (`arc.purge.chunked_required`) — authoritative cleanup never runs the
84
+ * unbounded fallback.
82
85
  * - `purgeByField` absent + hard strategy: legacy `deleteMany` fallback.
83
86
  * - `purgeByField` absent + non-hard strategy: returns `path: 'unsupported'`
84
87
  * with a clear error — soft/anonymize require the new primitive.
@@ -111,6 +114,18 @@ async function purgeResource(resourceName, tenantField, organizationId, resolved
111
114
  ...result.error ? { error: result.error } : {}
112
115
  };
113
116
  }
117
+ if (options.requireChunked) return {
118
+ resource: resourceName,
119
+ tenantField,
120
+ strategy,
121
+ processed: 0,
122
+ ok: false,
123
+ path: "unsupported",
124
+ error: {
125
+ code: "arc.purge.chunked_required",
126
+ message: `Resource '${resourceName}' requires chunked purge for authoritative cleanup, but its adapter repository does not implement \`purgeByField\`. The legacy unbounded \`deleteMany\` fallback is disabled here (no chunking / progress / abort). Upgrade the adapter (@classytic/mongokit ≥ 3.13.4 / @classytic/sqlitekit ≥ 0.3.4).`
127
+ }
128
+ };
114
129
  if (strategy.type !== "hard") return {
115
130
  resource: resourceName,
116
131
  tenantField,
@@ -425,7 +440,7 @@ var introspectionPlugin_default = fp(introspectionPlugin, { name: "arc-introspec
425
440
  //#endregion
426
441
  //#region src/registry/manifest.ts
427
442
  /**
428
- * Resource manifest for frontend code generators (v2.15.5).
443
+ * Resource manifest for frontend code generators.
429
444
  *
430
445
  * Every host that hand-rolls a `createCrudApi('foo')` helper on the frontend
431
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 };