@classytic/arc 2.20.0 → 2.22.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 (143) hide show
  1. package/dist/{BaseController-D-MHZ27_.mjs → BaseController-C6FlRWxF.mjs} +2 -2
  2. package/dist/{EventTransport-IEn7Tn-Q.d.mts → EventTransport-QmY7A258.d.mts} +0 -1
  3. package/dist/{QueryCache-TRfXau3W.d.mts → QueryCache-Z1B38koh.d.mts} +0 -1
  4. package/dist/{actionPermissions-C9RMiIpc.mjs → actionPermissions-ze70xXJi.mjs} +1 -1
  5. package/dist/audit/index.d.mts +9 -5
  6. package/dist/auth/audit.d.mts +7 -4
  7. package/dist/auth/audit.mjs +5 -0
  8. package/dist/auth/index.d.mts +35 -14
  9. package/dist/auth/index.mjs +6 -3
  10. package/dist/auth/redis-session.d.mts +1 -2
  11. package/dist/cache/index.d.mts +2 -3
  12. package/dist/cli/commands/describe.d.mts +1 -2
  13. package/dist/cli/commands/docs.mjs +1 -1
  14. package/dist/cli/commands/init.mjs +1 -1
  15. package/dist/cli/index.mjs +1 -1
  16. package/dist/context/index.d.mts +2 -2
  17. package/dist/core/index.d.mts +3 -3
  18. package/dist/core/index.mjs +4 -4
  19. package/dist/{core-CWhyGkiM.mjs → core-b6fgFgnd.mjs} +129 -17
  20. package/dist/{createActionRouter-HlQso0jO.mjs → createActionRouter-h1cEEaEX.mjs} +10 -6
  21. package/dist/{createAggregationRouter-5LzPzgw1.mjs → createAggregationRouter-B1C0kvTL.mjs} +3 -3
  22. package/dist/{createApp-D6uwzCKu.mjs → createApp-Ci7yG5gv.mjs} +390 -192
  23. package/dist/discovery/index.d.mts +0 -1
  24. package/dist/docs/index.d.mts +1 -2
  25. package/dist/docs/index.mjs +1 -1
  26. package/dist/{elevation-xGUBq6p_.d.mts → elevation-C9s5hFP_.d.mts} +0 -1
  27. package/dist/encryption/index.d.mts +2 -144
  28. package/dist/encryption/index.mjs +9 -5
  29. package/dist/{errorHandler-Dxf6P_kV.mjs → errorHandler-BTzXSwIg.mjs} +24 -3
  30. package/dist/{errorHandler-BtUUDPEa.d.mts → errorHandler-C6yv4ZTW.d.mts} +13 -1
  31. package/dist/{errors-DOHkjQlD.d.mts → errors-DHcHVAgk.d.mts} +0 -1
  32. package/dist/{eventPlugin-DbBFd3x_.d.mts → eventPlugin-A6EbthXR.d.mts} +10 -6
  33. package/dist/events/index.d.mts +9 -6
  34. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  35. package/dist/events/transports/redis.d.mts +1 -2
  36. package/dist/factory/index.d.mts +30 -9
  37. package/dist/factory/index.mjs +35 -4
  38. package/dist/{fastify-augmentation-CCQJEDgh.d.mts → fastify-augmentation-9_fuetB4.d.mts} +2 -3
  39. package/dist/fields-D3lhgzhD.d.mts +121 -0
  40. package/dist/health-ScyjIVo5.mjs +202 -0
  41. package/dist/hooks/index.d.mts +1 -1
  42. package/dist/idempotency/index.d.mts +4 -3
  43. package/dist/idempotency/index.mjs +2 -0
  44. package/dist/idempotency/redis.d.mts +1 -1
  45. package/dist/{types-BHMKlMBU.d.mts → index-BEu4wjbL.d.mts} +241 -80
  46. package/dist/{index-DUa8WHz3.d.mts → index-D8MKYGDa.d.mts} +88 -7
  47. package/dist/{index-_--GyzcO.d.mts → index-DbtgCOCj.d.mts} +1324 -1228
  48. package/dist/{index-CdFkIMEd.d.mts → index-ogIHvOjo.d.mts} +36 -7
  49. package/dist/index.d.mts +8 -7
  50. package/dist/index.mjs +6 -6
  51. package/dist/{init-YH_nslQR.mjs → init-DleK3p9f.mjs} +272 -139
  52. package/dist/integrations/event-gateway.d.mts +2 -3
  53. package/dist/integrations/index.d.mts +2 -2
  54. package/dist/integrations/jobs.d.mts +0 -1
  55. package/dist/integrations/mcp/index.d.mts +6 -7
  56. package/dist/integrations/mcp/index.mjs +1 -1
  57. package/dist/integrations/mcp/testing.d.mts +1 -2
  58. package/dist/integrations/mcp/testing.mjs +1 -1
  59. package/dist/integrations/streamline.d.mts +2 -2
  60. package/dist/integrations/webhooks.d.mts +8 -5
  61. package/dist/integrations/webhooks.mjs +9 -5
  62. package/dist/integrations/websocket-pushref-redis.d.mts +1 -2
  63. package/dist/integrations/websocket-redis.d.mts +1 -2
  64. package/dist/integrations/websocket.d.mts +2 -2
  65. package/dist/jsonBody-BlzAoGCe.mjs +43 -0
  66. package/dist/{loadResources-B8UmpITt.mjs → loadResources-DpU8pfjV.mjs} +4 -3
  67. package/dist/logger/index.d.mts +26 -1
  68. package/dist/logger/index.mjs +39 -1
  69. package/dist/{mcpPlugin-CuW6L_uD.mjs → mcpPlugin-ByA2tf6D.mjs} +8 -4
  70. package/dist/middleware/index.d.mts +1 -2
  71. package/dist/{module-JYH9V0I8.d.mts → module-B4u10iOJ.d.mts} +71 -1
  72. package/dist/{openapi-Dq6RbEGK.mjs → openapi-BRpMZD7C.mjs} +3 -3
  73. package/dist/permissions/index.d.mts +4 -3
  74. package/dist/permissions/index.mjs +2 -2
  75. package/dist/{permissions-CgbOLJZG.mjs → permissions-BJMuKMl7.mjs} +33 -1
  76. package/dist/pipeline/index.d.mts +1 -2
  77. package/dist/plugins/index.d.mts +96 -7
  78. package/dist/plugins/index.mjs +86 -268
  79. package/dist/plugins/response-cache.d.mts +5 -3
  80. package/dist/plugins/response-cache.mjs +1 -0
  81. package/dist/plugins/tracing-entry.d.mts +1 -1
  82. package/dist/plugins/tracing-entry.mjs +7 -4
  83. package/dist/presets/filesUpload.d.mts +4 -5
  84. package/dist/presets/filesUpload.mjs +1 -1
  85. package/dist/presets/index.d.mts +1 -2
  86. package/dist/presets/index.mjs +1 -1
  87. package/dist/presets/multiTenant.d.mts +1 -2
  88. package/dist/presets/search.d.mts +2 -3
  89. package/dist/presets/search.mjs +1 -1
  90. package/dist/{presets-DWsYQdbV.mjs → presets-gw0ciIUD.mjs} +1 -1
  91. package/dist/{pushref-registry-DnCqL3aa.d.mts → pushref-registry-BbcnpNTm.d.mts} +4 -2
  92. package/dist/{queryCachePlugin-xzFyKAjT.d.mts → queryCachePlugin-CfYWq-Ak.d.mts} +1 -2
  93. package/dist/rateLimitKey-BoWRH3e7.mjs +14 -0
  94. package/dist/{redis-C3W0qbz8.d.mts → redis-UDtLaNj6.d.mts} +0 -1
  95. package/dist/{redis-stream-DsP1vqZB.d.mts → redis-stream-iVR2l7aS.d.mts} +1 -2
  96. package/dist/{registerRoute-DGSREwPw.mjs → registerRoute-xHc42fwB.mjs} +38 -18
  97. package/dist/registry/index.d.mts +2 -2
  98. package/dist/requestId-5BhTVzBv.mjs +113 -0
  99. package/dist/schemaConverter-Ds5reSGs.mjs +168 -0
  100. package/dist/{schemaIR-ajetw3Fv.mjs → schemaIR-B-6SPe7g.mjs} +2 -2
  101. package/dist/schemas/index.d.mts +0 -1
  102. package/dist/scim/index.d.mts +0 -1
  103. package/dist/scim/index.mjs +6 -7
  104. package/dist/scope/index.d.mts +2 -3
  105. package/dist/scope/index.mjs +2 -76
  106. package/dist/scope-k15RXfcq.mjs +64 -0
  107. package/dist/{sessionManager-BXG-HGwG.d.mts → sessionManager-BHOM3N_0.d.mts} +10 -6
  108. package/dist/{storage-Co_PQpeQ.d.mts → storage-DLUZ04kK.d.mts} +8 -4
  109. package/dist/testing/index.d.mts +83 -4
  110. package/dist/testing/index.mjs +74 -4
  111. package/dist/testing/storageContract.d.mts +1 -2
  112. package/dist/{tracing-D8jlGXP7.d.mts → tracing-CsGKQJGg.d.mts} +0 -1
  113. package/dist/types/index.d.mts +4 -4
  114. package/dist/types/storage.d.mts +1 -1
  115. package/dist/{types-C9b2HR5W.d.mts → types-Bc42CUyk.d.mts} +1 -2
  116. package/dist/types-Cc91nXuA.d.mts +132 -0
  117. package/dist/{fields-C7ahdGYf.d.mts → types-Csxga7d3.d.mts} +2 -123
  118. package/dist/{types-LNebCoJx.d.mts → types-DH0GupI7.d.mts} +8 -4
  119. package/dist/usage/index.d.mts +142 -0
  120. package/dist/usage/index.mjs +0 -0
  121. package/dist/utils/index.d.mts +220 -144
  122. package/dist/utils/index.mjs +3 -3
  123. package/dist/{utils-ChjYQpjn.mjs → utils-FhJ88TJE.mjs} +129 -39
  124. package/dist/{versioning-D_eBCfNr.d.mts → versioning-DLwklPlG.d.mts} +1 -7
  125. package/dist/{websocket-MlYlrYq0.d.mts → websocket-uuWqM6UY.d.mts} +5 -4
  126. package/llms.txt +39 -0
  127. package/package.json +453 -447
  128. package/skills/arc/SKILL.md +14 -8
  129. package/skills/arc/references/api-reference.md +34 -0
  130. package/skills/arc/references/auth.md +21 -4
  131. package/skills/arc/references/mcp.md +1 -1
  132. package/skills/arc/references/production.md +44 -2
  133. package/skills/arc-code-review/SKILL.md +1 -1
  134. package/skills/arc-code-review/references/anti-patterns.md +71 -18
  135. package/skills/arc-code-review/references/arc-cheatsheet.md +15 -2
  136. package/skills/arc-code-review/references/migration-recipes.md +7 -7
  137. package/skills/arc-code-review/references/mongokit-migration.md +4 -3
  138. package/skills/arc-module-publishing/SKILL.md +243 -0
  139. package/skills/arc-module-publishing/references/authoring-modules.md +327 -0
  140. package/skills/arc-module-publishing/references/package-manifest.md +157 -0
  141. package/skills/arc-module-publishing/references/publishing-workflow.md +182 -0
  142. package/dist/preloadResources-CHw3_oA_.mjs +0 -45
  143. package/dist/schemaConverter-U0MnO9vd.mjs +0 -110
@@ -0,0 +1,157 @@
1
+ # package.json + tsdown reference
2
+
3
+ Copy-paste manifest for an arc ecosystem package, plus the dependency-field rules and the
4
+ optional-peer typing pattern. Values below are the real shapes shipped by
5
+ `@classytic/arc-approval` — adapt names, keep the structure.
6
+
7
+ ---
8
+
9
+ ## package.json
10
+
11
+ ```jsonc
12
+ {
13
+ "name": "@classytic/arc-<domain>",
14
+ "version": "0.1.0",
15
+ "description": "<one line — what this contributes to an arc host>",
16
+ "type": "module",
17
+
18
+ // Public entry. Add subpaths ("./adapter", "./testing") the same way if you split surfaces.
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.mts",
22
+ "default": "./dist/index.mjs"
23
+ }
24
+ },
25
+ "main": "./dist/index.mjs",
26
+ "types": "./dist/index.d.mts",
27
+
28
+ "sideEffects": false, // pure ESM, tree-shakable — no import-for-effect modules
29
+ "files": ["dist"], // ship only build output; never src/, tests/, configs
30
+ "publishConfig": { "access": "public" },
31
+ "engines": { "node": ">=22" },
32
+
33
+ "scripts": {
34
+ "build": "tsdown",
35
+ "typecheck": "tsc --noEmit",
36
+ "lint": "biome check src/",
37
+ "prepublishOnly": "npm run typecheck && npm run lint && npm run test && npm run build"
38
+ },
39
+
40
+ // CONTRACTS — what the host must provide. `>=`, never `^`.
41
+ // Floor >=2.21.0 if you use ResourceSeams / mergeResourceConfig / permissionMatrix /
42
+ // schedulesPlugin / errorMappers — declare the floor that matches the APIs you import.
43
+ "peerDependencies": {
44
+ "@classytic/arc": ">=2.21.0",
45
+ "@classytic/primitives": ">=0.9.0"
46
+ },
47
+
48
+ // Optional peers: declared here AND marked optional below. Host installs only if used.
49
+ // "peerDependencies": { ..., "zod": ">=3.23.0", "ioredis": ">=5.0.0" },
50
+ // "peerDependenciesMeta": {
51
+ // "zod": { "optional": true },
52
+ // "ioredis": { "optional": true }
53
+ // },
54
+
55
+ // TEST versions — pin with ^, floor >= each peer floor. Not shipped.
56
+ "devDependencies": {
57
+ "@classytic/arc": "^2.21.0",
58
+ "@classytic/primitives": "^0.9.1",
59
+ "@biomejs/biome": "^2.5.2",
60
+ "@types/node": "^22.19.21",
61
+ "tsdown": "^0.22.5",
62
+ "typescript": "^7.0.2",
63
+ "vitest": "^4.1.9"
64
+ },
65
+
66
+ "keywords": ["arc", "fastify", "<domain>", "classytic"],
67
+ "license": "MIT",
68
+ "author": "Classytic",
69
+ "repository": { "type": "git", "url": "https://github.com/classytic/arc-ecosystem.git", "directory": "packages/arc-<domain>" }
70
+ }
71
+ ```
72
+
73
+ In an **npm workspace**, devDeps common to all packages hoist to the root `package.json`; a
74
+ package only lists devDeps unique to it. The peer-skew gate reads root + package devDeps
75
+ together (see publishing-workflow.md).
76
+
77
+ ---
78
+
79
+ ## Dependency-field cheat sheet
80
+
81
+ | Field | What goes here | Range style | Why |
82
+ |---|---|---|---|
83
+ | `dependencies` | (usually nothing) truly-private bundled helpers only | `^` | Ships with you; host can't override. Almost never needed. |
84
+ | `peerDependencies` | arc, fastify, primitives, kits, zod, ioredis | **`>=`** | A contract/floor. Host resolves the real version; don't block their upgrades. |
85
+ | `peerDependenciesMeta` | mark optional peers `{ optional: true }` | — | Host installs only if they use that path. |
86
+ | `devDependencies` | concrete versions your tests run against | `^` | The version you actually exercise; floor must be `>=` peer floor. |
87
+
88
+ **The trap:** putting `@classytic/arc` or `fastify` in `dependencies`, or writing a peer as
89
+ `^2.20.0`. Both force the host into *your* version choice. A published arc package makes as
90
+ few decisions for the host as possible — it states floors and gets out of the way.
91
+
92
+ ---
93
+
94
+ ## tsdown.config.ts
95
+
96
+ ```ts
97
+ import { defineConfig } from "tsdown";
98
+
99
+ export default defineConfig({
100
+ entry: ["src/index.ts"], // add entries for extra subpaths: ["src/index.ts", "src/adapter.ts"]
101
+ format: ["esm"], // ESM only — arc is ESM only
102
+ dts: true, // emit .d.mts
103
+ sourcemap: false,
104
+ clean: true,
105
+ treeshake: true,
106
+ target: "node22",
107
+ outDir: "dist",
108
+ deps: {
109
+ // Bundle NOTHING — same never-bundle contract as arc's own tsdown config.
110
+ skipNodeModulesBundle: true,
111
+ neverBundle: [/^@classytic\//],
112
+ },
113
+ });
114
+ ```
115
+
116
+ Why bundle nothing: if you inline `@classytic/arc` (or primitives, or a kit), the host ends up
117
+ with two copies — yours frozen in `dist/`, theirs in `node_modules`. Arc's plugin registry,
118
+ event transport, and scope decorators are identity-sensitive; two copies register twice and
119
+ diverge. `neverBundle: [/^@classytic\//]` guarantees the host's single installed copy is the
120
+ only one at runtime.
121
+
122
+ ---
123
+
124
+ ## Optional peers: keep their types out of your public surface
125
+
126
+ If a peer is optional (a host might not install it), its types **must not** appear in your
127
+ published `.d.mts` — otherwise a host without it gets a compile error importing your package.
128
+ Use structural typing: describe the shape you need with a local interface, don't import the
129
+ dependency's types into the public boundary.
130
+
131
+ ```ts
132
+ // ❌ leaks zod into dist/index.d.mts — breaks hosts that didn't install zod
133
+ import type { ZodType } from "zod";
134
+ export function withValidation(schema: ZodType) { /* ... */ }
135
+
136
+ // ✅ structural: the public signature owes nothing to zod's package
137
+ export interface StandardSchemaLike<T> {
138
+ readonly "~standard": { validate: (v: unknown) => { value: T } | { issues: unknown[] } };
139
+ }
140
+ export function withValidation<T>(schema: StandardSchemaLike<T>) { /* ... */ }
141
+ ```
142
+
143
+ For **shipped action/route schemas, use JSON Schema**, not zod objects. Arc converts JSON
144
+ Schema natively; zod stays an *optional* convenience peer for hosts that prefer it. A package
145
+ that hard-requires zod for its schemas has made zod a non-optional dependency by the back door.
146
+
147
+ ```ts
148
+ schema: {
149
+ type: "object",
150
+ required: ["stepId", "decision"],
151
+ properties: {
152
+ stepId: { type: "string", minLength: 1 },
153
+ decision: { type: "string", enum: ["approved", "rejected"] },
154
+ },
155
+ additionalProperties: false,
156
+ }
157
+ ```
@@ -0,0 +1,182 @@
1
+ # Publishing workflow
2
+
3
+ Monorepo layout, the release gates, and the classytic bot-identity publish steps.
4
+
5
+ ---
6
+
7
+ ## Monorepo layout (npm workspaces)
8
+
9
+ Prefer **one workspace holding many small packages** over one repo per package — they release
10
+ together and share tooling. The `@classytic/arc-ecosystem` layout:
11
+
12
+ ```
13
+ arc-ecosystem/
14
+ package.json # workspaces: ["packages/*"], shared devDeps, verify script
15
+ scripts/
16
+ check-peer-skew.mjs # gate: every peer floor covered by a devDep floor
17
+ prepublish-gate.mjs # per-package publish gate
18
+ packages/
19
+ arc-approval/ # @classytic/arc-approval — one package, one surface
20
+ package.json
21
+ tsdown.config.ts
22
+ src/index.ts
23
+ arc-involvement/
24
+ arc-notifications/
25
+ ```
26
+
27
+ Root `package.json` scripts:
28
+
29
+ ```jsonc
30
+ {
31
+ "workspaces": ["packages/*"],
32
+ "scripts": {
33
+ "build": "npm run build --workspaces",
34
+ "typecheck": "npm run typecheck --workspaces",
35
+ "lint": "biome check packages/",
36
+ "test": "vitest run",
37
+ "check:peer-skew": "node scripts/check-peer-skew.mjs",
38
+ "verify": "npm run check:peer-skew && npm run typecheck && npm run lint && npm run test && npm run build"
39
+ }
40
+ }
41
+ ```
42
+
43
+ Shared devDeps (arc, primitives, biome, tsdown, typescript, vitest, fastify, @types/node) live
44
+ in the **root** `package.json` so every package tests against one aligned set. During
45
+ pre-publish development the root can carry `"@classytic/arc": "file:../arc"` to test against
46
+ unreleased arc; **switch it to the published range (`"^2.20.0"`) once arc ships** and reinstall
47
+ so packages resolve arc from the registry tarball, not the local symlink.
48
+
49
+ **pnpm + turborepo + changesets** is the heavier-duty alternative to npm workspaces once the
50
+ package count grows: turbo gives cached, topo-ordered builds (`dependsOn: ["^build"]`);
51
+ changesets drive versioning + changelogs (`access: "public"`, `updateInternalDependencies:
52
+ "patch"`). Wire the peer-skew gate into the release path so it can't be skipped — e.g.
53
+ `"release": "pnpm run check:peer-skew && turbo typecheck test build && changeset publish"`.
54
+ Local workspace links use `workspace:*` (pnpm) instead of `file:`; the peer-skew gate treats
55
+ `workspace:` / `link:` / `file:` as covering any floor (the checked-out source *is* the version
56
+ under development).
57
+
58
+ ### Supply-chain hygiene (.npmrc / pnpm-workspace.yaml)
59
+
60
+ Belt-and-suspenders settings worth adopting for a published ecosystem:
61
+
62
+ ```ini
63
+ # .npmrc
64
+ auto-install-peers=false # peers are the HOST's duty — never silently fetch them
65
+ save-workspace-protocol=rolled
66
+ ```
67
+
68
+ ```yaml
69
+ # pnpm-workspace.yaml
70
+ onlyBuiltDependencies: # allowlist packages permitted to run install scripts
71
+ - esbuild
72
+ - mongodb-memory-server
73
+ minimumReleaseAgeExclude: # cooldown on brand-new registry versions, with explicit escapes
74
+ - '@classytic/repo-core@0.7.0'
75
+ ```
76
+
77
+ - `auto-install-peers=false` enforces the contract mechanically: if a host forgets a peer, they
78
+ get an error, not a silently-resolved wrong version. It also stops your *dev* environment from
79
+ masking a missing peer declaration.
80
+ - `onlyBuiltDependencies` is a lifecycle-script allowlist — nothing runs `postinstall` unless you
81
+ named it. A cheap, real guard against a compromised transitive dep executing on `pnpm install`.
82
+ - A `minimumReleaseAge` cooldown blunts the "malicious version published minutes ago" attack;
83
+ the `Exclude` list is your escape hatch for versions you've vetted.
84
+
85
+ ---
86
+
87
+ ## Gate 1 — peer-skew (monorepo)
88
+
89
+ `scripts/check-peer-skew.mjs` fails the build if any package declares a peer floor higher than
90
+ the devDep floor it actually tests against. Without it, a package can claim `">=2.20.0"` while
91
+ its suite runs against 2.18 — a lie that ships. The gate reads root + package devDeps together
92
+ (`file:` links count as covering any floor, since the link *is* the version under development).
93
+
94
+ ```js
95
+ // Core check, per package, per peer:
96
+ // devFloor = floorOf(devDeps[name]) // from root + package devDeps merged
97
+ // peerFloor = floorOf(peers[name])
98
+ // if (devFloor < peerFloor) FAIL // suite tests below what you promise
99
+ // Optional peers (peerDependenciesMeta.optional) with no devDep are allowed to be absent.
100
+ ```
101
+
102
+ Run it first in `verify` so a skew fails fast before the slower typecheck/test/build.
103
+
104
+ ---
105
+
106
+ ## Gate 2 — prepublish (per package)
107
+
108
+ No package publishes without a green gate. No `--no-verify`, ever.
109
+
110
+ - **Solo package:** `"prepublishOnly": "npm run typecheck && npm run lint && npm run test && npm run build"`.
111
+ - **Workspace:** a shared `scripts/prepublish-gate.mjs` invoked from each package's
112
+ `prepublishOnly`, running the full workspace `verify` (peer-skew + typecheck + lint + test +
113
+ build) so no package ships against a skewed or broken tree.
114
+
115
+ The gate is the contract with consumers: if it's green, the published `dist/` was typechecked,
116
+ linted, tested, and built from the source in the same commit.
117
+
118
+ ---
119
+
120
+ ## Release — classytic packages (bot identity)
121
+
122
+ Every commit in a classytic package is authored as **`classytic-bot[bot]`** — the maintainer
123
+ identity, never an individual. **Per-invocation git env vars only; never `git config
124
+ --global`; no `Co-Authored-By` trailers.**
125
+
126
+ ```bash
127
+ GIT_AUTHOR_NAME="classytic-bot[bot]" \
128
+ GIT_AUTHOR_EMAIL="278929599+classytic-bot[bot]@users.noreply.github.com" \
129
+ GIT_COMMITTER_NAME="classytic-bot[bot]" \
130
+ GIT_COMMITTER_EMAIL="278929599+classytic-bot[bot]@users.noreply.github.com" \
131
+ git commit -m "feat(arc-x): …"
132
+ ```
133
+
134
+ Push through `@classytic/dev-tools` so the commit and pusher both carry the `[bot]` badge (it
135
+ mints a short-lived GitHub App installation token, pushes, and never persists it):
136
+
137
+ ```bash
138
+ npm run push -- main # push current work to main
139
+ ```
140
+
141
+ Publish to **public npm** (`publishConfig.access: "public"` is already set):
142
+
143
+ ```bash
144
+ npm publish -w @classytic/arc-<domain> # prepublishOnly gate runs automatically
145
+ ```
146
+
147
+ Third-party (non-classytic) packages: normal git identity, same gates and same
148
+ public/scoped-npm publish — only the bot identity and `classytic-push` are classytic-specific.
149
+
150
+ ---
151
+
152
+ ## Versioning a public export
153
+
154
+ SemVer the **contract**, not the internals:
155
+
156
+ | Change | Bump |
157
+ |---|---|
158
+ | New optional config field, new exported helper, new action | **minor** |
159
+ | Raising a peer floor (e.g. now requires arc ≥2.21) | **minor** (it narrows the host set) |
160
+ | Changing an exported function signature, removing an export, renaming an action/field | **major** |
161
+ | Internal refactor, dep bump within range, doc/test-only | **patch** |
162
+
163
+ The `ArcModule` shape you return, your exported function signatures, and your action/schema
164
+ surface are the contract consumers pin against. Internals (private helpers, how the engine is
165
+ built) are free to change under a patch.
166
+
167
+ ---
168
+
169
+ ## Common publish failures (and the fix)
170
+
171
+ - **Peer written as `^` instead of `>=`** → blocks hosts on a newer major that still satisfies
172
+ your API. Fix: `">=X.Y.0"`.
173
+ - **arc/fastify in `dependencies`** → host gets a bundled duplicate; plugin registry splits.
174
+ Fix: move to `peerDependencies`.
175
+ - **Optional-peer types in `dist/*.d.mts`** → hosts without that peer fail to compile your
176
+ package. Fix: structural typing at the public boundary (see package-manifest.md).
177
+ - **Deep import (`@classytic/arc/src/...`)** → breaks on any arc internal reorg. Fix: use a
178
+ public subpath, or file the missing export against arc.
179
+ - **A back-edge: arc depending on your package** → un-publishable cycle. Fix: the abstraction
180
+ belongs in arc as a port/hook, not as a dependency on you.
181
+ - **Skewed devDep below peer floor** → suite tests a version you claim to support but don't.
182
+ Fix: raise the devDep, reinstall, re-run peer-skew.
@@ -1,45 +0,0 @@
1
- //#region src/testing/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,110 +0,0 @@
1
- //#region src/utils/schemaConverter.ts
2
- /** Default target for Fastify-consumed schemas (matches Fastify v5's default AJV draft). */
3
- const DEFAULT_FASTIFY_TARGET = "draft-7";
4
- /** Default target for OpenAPI document generation (matches arc's emitted OpenAPI version). */
5
- const DEFAULT_OPENAPI_TARGET = "openapi-3.0";
6
- let _toJSONSchema = null;
7
- import("zod").then(({ z }) => {
8
- if (typeof z?.toJSONSchema === "function") _toJSONSchema = (schema, opts) => z.toJSONSchema(schema, opts);
9
- }).catch(() => {});
10
- /**
11
- * Check if an object is already a plain JSON Schema.
12
- * Returns true if it has JSON Schema markers (`type`, `properties`, `$ref`,
13
- * `allOf`, `anyOf`, `oneOf`, `items`, `enum`) and does NOT have Zod markers.
14
- */
15
- function isJsonSchema(input) {
16
- if (input === null || typeof input !== "object") return false;
17
- const obj = input;
18
- if ("_def" in obj || "_zod" in obj) return false;
19
- return "type" in obj || "properties" in obj || "$ref" in obj || "allOf" in obj || "anyOf" in obj || "oneOf" in obj || "items" in obj || "enum" in obj;
20
- }
21
- /**
22
- * Check if an object is a Zod schema (has `_zod` marker from Zod v4).
23
- */
24
- function isZodSchema(input) {
25
- return input !== null && typeof input === "object" && "_zod" in input;
26
- }
27
- /**
28
- * Convert any schema input to JSON Schema.
29
- *
30
- * Detection order:
31
- * 1. `null`/`undefined` → `undefined`
32
- * 2. Already JSON Schema → pass through as-is (zero overhead)
33
- * 3. Zod v4 schema → `z.toJSONSchema(schema, { target })`
34
- * 4. Unrecognized object → return as-is (treat as opaque schema)
35
- *
36
- * @param input Schema (Zod, plain JSON Schema, or opaque object)
37
- * @param target Output target — defaults to `draft-7` for Fastify compatibility.
38
- * Pass `openapi-3.0`/`openapi-3.1` for OpenAPI document generation.
39
- */
40
- function toJsonSchema(input, target = DEFAULT_FASTIFY_TARGET) {
41
- if (input == null) return void 0;
42
- if (typeof input !== "object") return void 0;
43
- if (isJsonSchema(input)) return input;
44
- if (isZodSchema(input)) {
45
- if (!_toJSONSchema) return input;
46
- try {
47
- const converted = _toJSONSchema(input, { target });
48
- if ("$schema" in converted) delete converted.$schema;
49
- return converted;
50
- } catch {
51
- return { type: "object" };
52
- }
53
- }
54
- return input;
55
- }
56
- /**
57
- * Convert all schema fields in an OpenApiSchemas object.
58
- * JSON Schema values pass through unchanged. Only Zod schemas are converted.
59
- *
60
- * Defaults to the `openapi-3.0` target since this function feeds OpenAPI doc
61
- * generation, not Fastify route validation.
62
- */
63
- function convertOpenApiSchemas(schemas, target = DEFAULT_OPENAPI_TARGET) {
64
- const result = {};
65
- const schemaFields = [
66
- "entity",
67
- "createBody",
68
- "updateBody",
69
- "params",
70
- "listQuery",
71
- "response"
72
- ];
73
- for (const field of schemaFields) {
74
- const value = schemas[field];
75
- if (value !== void 0) result[field] = toJsonSchema(value, target) ?? value;
76
- }
77
- for (const [key, value] of Object.entries(schemas)) if (!schemaFields.includes(key)) result[key] = value;
78
- return result;
79
- }
80
- /**
81
- * Convert schema values in a Fastify route schema record.
82
- *
83
- * Handles `body`, `querystring`, `params`, `headers` (top-level conversion)
84
- * and `response` (iterates by status code — each value converted individually).
85
- *
86
- * JSON Schema values pass through unchanged. Only Zod schemas are converted.
87
- *
88
- * Used for both custom routes and customSchemas (CRUD overrides).
89
- *
90
- * Defaults to `draft-7` so Fastify v5's bundled AJV 8 accepts the output.
91
- * Pass `openapi-3.0` (or `openapi-3.1`) when generating OpenAPI documents.
92
- */
93
- function convertRouteSchema(schema, target = DEFAULT_FASTIFY_TARGET) {
94
- const result = { ...schema };
95
- for (const field of [
96
- "body",
97
- "querystring",
98
- "params",
99
- "headers"
100
- ]) if (result[field] !== void 0) result[field] = toJsonSchema(result[field], target) ?? result[field];
101
- if (result.response !== void 0 && typeof result.response === "object" && result.response !== null) {
102
- const responseObj = result.response;
103
- const convertedResponse = {};
104
- for (const [statusCode, responseSchema] of Object.entries(responseObj)) convertedResponse[statusCode] = toJsonSchema(responseSchema, target) ?? responseSchema;
105
- result.response = convertedResponse;
106
- }
107
- return result;
108
- }
109
- //#endregion
110
- export { toJsonSchema as a, isZodSchema as i, convertRouteSchema as n, isJsonSchema as r, convertOpenApiSchemas as t };