@saacms/core 0.1.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 (116) hide show
  1. package/README.md +25 -0
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/access/index.d.ts +37 -0
  4. package/dist/access/index.d.ts.map +1 -0
  5. package/dist/access/index.js +6 -0
  6. package/dist/auth/index.d.ts +30 -0
  7. package/dist/auth/index.d.ts.map +1 -0
  8. package/dist/codegen/content-migration.d.ts +167 -0
  9. package/dist/codegen/content-migration.d.ts.map +1 -0
  10. package/dist/codegen/filter-openapi-for-user.d.ts +100 -0
  11. package/dist/codegen/filter-openapi-for-user.d.ts.map +1 -0
  12. package/dist/codegen/index.d.ts +19 -0
  13. package/dist/codegen/index.d.ts.map +1 -0
  14. package/dist/codegen/index.js +43 -0
  15. package/dist/codegen/openapi-types.d.ts +125 -0
  16. package/dist/codegen/openapi-types.d.ts.map +1 -0
  17. package/dist/codegen/to-d1-migration.d.ts +88 -0
  18. package/dist/codegen/to-d1-migration.d.ts.map +1 -0
  19. package/dist/codegen/to-drizzle.d.ts +131 -0
  20. package/dist/codegen/to-drizzle.d.ts.map +1 -0
  21. package/dist/codegen/to-openapi.d.ts +80 -0
  22. package/dist/codegen/to-openapi.d.ts.map +1 -0
  23. package/dist/codegen/to-puck-fields.d.ts +109 -0
  24. package/dist/codegen/to-puck-fields.d.ts.map +1 -0
  25. package/dist/codegen/to-ts-types.d.ts +59 -0
  26. package/dist/codegen/to-ts-types.d.ts.map +1 -0
  27. package/dist/hooks/index.d.ts +94 -0
  28. package/dist/hooks/index.d.ts.map +1 -0
  29. package/dist/hooks/index.js +8 -0
  30. package/dist/host/index.d.ts +109 -0
  31. package/dist/host/index.d.ts.map +1 -0
  32. package/dist/host/index.js +16 -0
  33. package/dist/index-172n82sz.js +4 -0
  34. package/dist/index-8g8ymd37.js +275 -0
  35. package/dist/index-a3pnt8yz.js +1494 -0
  36. package/dist/index-b59hfany.js +3078 -0
  37. package/dist/index-b7z43xwp.js +6 -0
  38. package/dist/index-r0at8zaw.js +13 -0
  39. package/dist/index-zgbq60fy.js +74 -0
  40. package/dist/index.d.ts +23 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +261 -0
  43. package/dist/observability/audit.d.ts +65 -0
  44. package/dist/observability/audit.d.ts.map +1 -0
  45. package/dist/observability/index.d.ts +2 -0
  46. package/dist/observability/index.d.ts.map +1 -0
  47. package/dist/publish/compile.d.ts +76 -0
  48. package/dist/publish/compile.d.ts.map +1 -0
  49. package/dist/runtime/auth-middleware.d.ts +34 -0
  50. package/dist/runtime/auth-middleware.d.ts.map +1 -0
  51. package/dist/runtime/boolean-columns.d.ts +65 -0
  52. package/dist/runtime/boolean-columns.d.ts.map +1 -0
  53. package/dist/runtime/cache.d.ts +62 -0
  54. package/dist/runtime/cache.d.ts.map +1 -0
  55. package/dist/runtime/create-route.d.ts +26 -0
  56. package/dist/runtime/create-route.d.ts.map +1 -0
  57. package/dist/runtime/delete-route.d.ts +15 -0
  58. package/dist/runtime/delete-route.d.ts.map +1 -0
  59. package/dist/runtime/drafts-route.d.ts +65 -0
  60. package/dist/runtime/drafts-route.d.ts.map +1 -0
  61. package/dist/runtime/health-route.d.ts +23 -0
  62. package/dist/runtime/health-route.d.ts.map +1 -0
  63. package/dist/runtime/index.d.ts +24 -0
  64. package/dist/runtime/index.d.ts.map +1 -0
  65. package/dist/runtime/index.js +17 -0
  66. package/dist/runtime/json-columns.d.ts +50 -0
  67. package/dist/runtime/json-columns.d.ts.map +1 -0
  68. package/dist/runtime/list-route.d.ts +20 -0
  69. package/dist/runtime/list-route.d.ts.map +1 -0
  70. package/dist/runtime/openapi-route.d.ts +30 -0
  71. package/dist/runtime/openapi-route.d.ts.map +1 -0
  72. package/dist/runtime/pattern-route.d.ts +48 -0
  73. package/dist/runtime/pattern-route.d.ts.map +1 -0
  74. package/dist/runtime/problem-details.d.ts +32 -0
  75. package/dist/runtime/problem-details.d.ts.map +1 -0
  76. package/dist/runtime/put-route.d.ts +19 -0
  77. package/dist/runtime/put-route.d.ts.map +1 -0
  78. package/dist/runtime/read-route.d.ts +26 -0
  79. package/dist/runtime/read-route.d.ts.map +1 -0
  80. package/dist/runtime/scale-cost.d.ts +84 -0
  81. package/dist/runtime/scale-cost.d.ts.map +1 -0
  82. package/dist/runtime/scheme-route.d.ts +49 -0
  83. package/dist/runtime/scheme-route.d.ts.map +1 -0
  84. package/dist/runtime/services.d.ts +42 -0
  85. package/dist/runtime/services.d.ts.map +1 -0
  86. package/dist/runtime/update-route.d.ts +20 -0
  87. package/dist/runtime/update-route.d.ts.map +1 -0
  88. package/dist/runtime/upload-route.d.ts +46 -0
  89. package/dist/runtime/upload-route.d.ts.map +1 -0
  90. package/dist/schema/index.d.ts +185 -0
  91. package/dist/schema/index.d.ts.map +1 -0
  92. package/dist/schema/index.js +40 -0
  93. package/dist/schema/media.d.ts +237 -0
  94. package/dist/schema/media.d.ts.map +1 -0
  95. package/dist/schema/plugin-trust.d.ts +144 -0
  96. package/dist/schema/plugin-trust.d.ts.map +1 -0
  97. package/dist/signals/index.d.ts +10 -0
  98. package/dist/signals/index.d.ts.map +1 -0
  99. package/dist/signals/index.js +10 -0
  100. package/dist/storage/index.d.ts +120 -0
  101. package/dist/storage/index.d.ts.map +1 -0
  102. package/dist/storage/index.js +13 -0
  103. package/dist/tenant/index.d.ts +105 -0
  104. package/dist/tenant/index.d.ts.map +1 -0
  105. package/dist/theme/index.d.ts +56 -0
  106. package/dist/theme/index.d.ts.map +1 -0
  107. package/dist/types/ids.d.ts +45 -0
  108. package/dist/types/ids.d.ts.map +1 -0
  109. package/dist/types/ids.js +15 -0
  110. package/dist/types/index.d.ts +3 -0
  111. package/dist/types/index.d.ts.map +1 -0
  112. package/dist/types/index.js +6 -0
  113. package/dist/types/user.d.ts +14 -0
  114. package/dist/types/user.d.ts.map +1 -0
  115. package/dist/types/user.js +1 -0
  116. package/package.json +116 -0
@@ -0,0 +1,275 @@
1
+ // src/schema/media.ts
2
+ import { Schema, SchemaAST as AST } from "effect";
3
+ var IntField = Schema.Number.annotations({ saacmsInt: true });
4
+ var _CommonMediaFields = Schema.Struct({
5
+ mime: Schema.String,
6
+ size: IntField,
7
+ width: IntField,
8
+ height: IntField,
9
+ uploadedBy: Schema.String
10
+ });
11
+ var _BucketOnlyFields = Schema.Struct({
12
+ originalKey: Schema.String,
13
+ originalUrl: Schema.String
14
+ });
15
+ var _RepoOnlyFields = Schema.Struct({
16
+ repoPath: Schema.String,
17
+ publicUrl: Schema.String
18
+ });
19
+ var MediaFields = Schema.Struct({
20
+ mime: Schema.String,
21
+ size: IntField,
22
+ width: IntField,
23
+ height: IntField,
24
+ uploadedBy: Schema.String,
25
+ originalKey: Schema.String,
26
+ originalUrl: Schema.String
27
+ });
28
+ function extractSigs(schema) {
29
+ const ast = schema.ast;
30
+ if (!AST.isTypeLiteral(ast)) {
31
+ throw new Error("extractSigs: expected a Struct (TypeLiteral)");
32
+ }
33
+ return ast.propertySignatures;
34
+ }
35
+ var _COMMON_SIGS = extractSigs(_CommonMediaFields);
36
+ var _BUCKET_ONLY_SIGS = extractSigs(_BucketOnlyFields);
37
+ var _REPO_ONLY_SIGS = extractSigs(_RepoOnlyFields);
38
+ var _BUCKET_ALL_SIGS = [
39
+ ..._COMMON_SIGS,
40
+ ..._BUCKET_ONLY_SIGS
41
+ ];
42
+ var _REPO_ALL_SIGS = [
43
+ ..._COMMON_SIGS,
44
+ ..._REPO_ONLY_SIGS
45
+ ];
46
+ var MEDIA_FIELD_NAMES = _BUCKET_ALL_SIGS.map((p) => String(p.name));
47
+ var REPO_MEDIA_FIELD_NAMES = _REPO_ALL_SIGS.map((p) => String(p.name));
48
+ function withMediaFields(coll) {
49
+ if (coll.kind !== "media")
50
+ return coll;
51
+ const authorAst = coll.schema.ast;
52
+ if (!AST.isTypeLiteral(authorAst)) {
53
+ throw new Error(`withMediaFields: root schema for media collection "${coll.slug}" ` + `must be a Struct (TypeLiteral); got ${authorAst._tag}`);
54
+ }
55
+ const mode = coll.storage?.mode ?? "bucket";
56
+ const injectedSigs = mode === "repo" ? _REPO_ALL_SIGS : _BUCKET_ALL_SIGS;
57
+ const reservedNames = mode === "repo" ? REPO_MEDIA_FIELD_NAMES : MEDIA_FIELD_NAMES;
58
+ const authorNames = new Set(authorAst.propertySignatures.map((p) => String(p.name)));
59
+ for (const name of reservedNames) {
60
+ if (authorNames.has(name)) {
61
+ throw new Error(`withMediaFields: collection "${coll.slug}" re-declares reserved ` + `media field "${name}". The ADR-0009 auto-injected fields ` + `(${reservedNames.join(", ")}) are part of a media ` + `Collection's structure and cannot be author-declared.`);
62
+ }
63
+ }
64
+ const merged = new AST.TypeLiteral([...authorAst.propertySignatures, ...injectedSigs], authorAst.indexSignatures, authorAst.annotations);
65
+ return { ...coll, schema: Schema.make(merged) };
66
+ }
67
+ function MediaRef(slug) {
68
+ const base = Schema.Struct({
69
+ id: Schema.String,
70
+ originalUrl: Schema.String,
71
+ mime: Schema.String,
72
+ width: IntField,
73
+ height: IntField
74
+ });
75
+ return slug == null ? base : base.annotations({ description: `MediaRef → ${slug}` });
76
+ }
77
+ function mediaTransformUrl(baseUrl, transform) {
78
+ const options = buildOptions(transform);
79
+ if (options === "")
80
+ return baseUrl;
81
+ const prefix = `cdn-cgi/image/${options}`;
82
+ let parsed;
83
+ try {
84
+ parsed = new URL(baseUrl);
85
+ } catch {
86
+ parsed = undefined;
87
+ }
88
+ if (parsed !== undefined) {
89
+ return `${parsed.origin}/${prefix}${parsed.pathname}${parsed.search}`;
90
+ }
91
+ const path = baseUrl.startsWith("/") ? baseUrl : `/${baseUrl}`;
92
+ return `/${prefix}${path}`;
93
+ }
94
+ function buildOptions(transform) {
95
+ if (transform === undefined)
96
+ return "";
97
+ const parts = [];
98
+ if (transform.width !== undefined)
99
+ parts.push(`width=${transform.width}`);
100
+ if (transform.height !== undefined)
101
+ parts.push(`height=${transform.height}`);
102
+ if (transform.format !== undefined)
103
+ parts.push(`format=${transform.format}`);
104
+ if (transform.quality !== undefined) {
105
+ parts.push(`quality=${transform.quality}`);
106
+ }
107
+ return parts.join(",");
108
+ }
109
+ function resolveMediaRef(record) {
110
+ const srcset = () => {
111
+ throw new Error("MediaRef.srcset is NOT_IMPLEMENTED in v1 (srcset is a documented " + "v1.x follow-up). See ADR 0009 §6 / §5. Use .url({ width }) per " + "breakpoint until then.");
112
+ };
113
+ if ("publicUrl" in record) {
114
+ return {
115
+ id: record.id,
116
+ mime: record.mime,
117
+ width: record.width,
118
+ height: record.height,
119
+ url: (_transform) => record.publicUrl,
120
+ srcset
121
+ };
122
+ }
123
+ return {
124
+ id: record.id,
125
+ mime: record.mime,
126
+ width: record.width,
127
+ height: record.height,
128
+ url: (transform) => mediaTransformUrl(record.originalUrl, transform),
129
+ srcset
130
+ };
131
+ }
132
+ async function mediaRepoName(opts) {
133
+ const policy = opts.policy ?? "content-hash";
134
+ if (policy === "slug") {
135
+ const slug = toSlug(opts.originalFilename ?? "");
136
+ if (slug !== "")
137
+ return `${slug}.${opts.ext}`;
138
+ }
139
+ const hashBuffer = await crypto.subtle.digest("SHA-256", opts.bytes);
140
+ const hex = Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
141
+ return `${hex.slice(0, 16)}.${opts.ext}`;
142
+ }
143
+ function toSlug(filename) {
144
+ const base = filename.replace(/\.[^.]+$/, "");
145
+ return base.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
146
+ }
147
+ function mimeToExt(mime, filename) {
148
+ if (filename) {
149
+ const m = filename.match(/\.([^.]+)$/);
150
+ if (m)
151
+ return m[1].toLowerCase();
152
+ }
153
+ const MAP = {
154
+ "image/png": "png",
155
+ "image/jpeg": "jpg",
156
+ "image/webp": "webp",
157
+ "image/gif": "gif",
158
+ "image/avif": "avif",
159
+ "image/svg+xml": "svg",
160
+ "image/tiff": "tiff",
161
+ "application/pdf": "pdf"
162
+ };
163
+ return MAP[mime] ?? "bin";
164
+ }
165
+ async function stageRepoMedia(opts) {
166
+ assertPathSafe(opts.collectionSlug, "collectionSlug");
167
+ if (opts.originalFilename !== undefined) {
168
+ assertPathSafe(opts.originalFilename, "originalFilename");
169
+ }
170
+ const root = opts.assetRoot.endsWith("/") ? opts.assetRoot : `${opts.assetRoot}/`;
171
+ const ext = mimeToExt(opts.mime, opts.originalFilename);
172
+ const name = await mediaRepoName({
173
+ bytes: opts.bytes,
174
+ originalFilename: opts.originalFilename,
175
+ ext,
176
+ policy: opts.policy
177
+ });
178
+ const repoPath = `${root}saacms/${opts.collectionSlug}/${name}`;
179
+ const publicUrl = `/saacms/${opts.collectionSlug}/${name}`;
180
+ const bytes = opts.bytes instanceof Uint8Array ? opts.bytes : new Uint8Array(opts.bytes);
181
+ return { repoPath, publicUrl, bytes };
182
+ }
183
+ function assertPathSafe(value, label) {
184
+ if (value.includes("..") || value.startsWith("/") || value.includes("\\")) {
185
+ throw new Error(`stageRepoMedia: unsafe ${label} "${value}" — ` + `reject "..", absolute paths, and backslashes.`);
186
+ }
187
+ }
188
+ // src/schema/plugin-trust.ts
189
+ var PLUGIN_CAPABILITIES = [
190
+ "collections",
191
+ "blocks",
192
+ "pages",
193
+ "routes",
194
+ "hooks",
195
+ "services",
196
+ "storage:read",
197
+ "storage:write",
198
+ "admin-pages",
199
+ "migrations"
200
+ ];
201
+ function deriveContributedCapabilities(plugin) {
202
+ const used = new Set;
203
+ const nonEmptyArr = (v) => Array.isArray(v) && v.length > 0;
204
+ const nonEmptyRec = (v) => v != null && Object.keys(v).length > 0;
205
+ if (nonEmptyArr(plugin.collections)) {
206
+ used.add("collections");
207
+ used.add("storage:read");
208
+ used.add("storage:write");
209
+ }
210
+ if (nonEmptyArr(plugin.blocks))
211
+ used.add("blocks");
212
+ if (nonEmptyArr(plugin.pages))
213
+ used.add("pages");
214
+ if (nonEmptyArr(plugin.pageTemplates))
215
+ used.add("pages");
216
+ if (nonEmptyRec(plugin.routes))
217
+ used.add("routes");
218
+ if (nonEmptyRec(plugin.hooks))
219
+ used.add("hooks");
220
+ if (nonEmptyRec(plugin.services))
221
+ used.add("services");
222
+ if (nonEmptyArr(plugin.admin?.pages) || nonEmptyArr(plugin.admin?.menu)) {
223
+ used.add("admin-pages");
224
+ }
225
+ if (plugin.migrations?.db != null || plugin.migrations?.content != null) {
226
+ used.add("migrations");
227
+ }
228
+ if (plugin.init != null)
229
+ used.add("storage:write");
230
+ return PLUGIN_CAPABILITIES.filter((c) => used.has(c));
231
+ }
232
+ function assertPluginCapabilities(config, opts = {}) {
233
+ const { strict = false } = opts;
234
+ const violations = [];
235
+ const undeclared = [];
236
+ for (const plugin of config.plugins ?? []) {
237
+ const name = String(plugin.name);
238
+ const used = deriveContributedCapabilities(plugin);
239
+ if (plugin.capabilities == null) {
240
+ if (used.length > 0)
241
+ undeclared.push(name);
242
+ continue;
243
+ }
244
+ const declaredSet = new Set(plugin.capabilities);
245
+ for (const cap of used) {
246
+ if (!declaredSet.has(cap)) {
247
+ violations.push({ plugin: name, used: cap, declared: false });
248
+ }
249
+ }
250
+ }
251
+ const ok = violations.length === 0 && (!strict || undeclared.length === 0);
252
+ return { ok, violations, undeclared };
253
+ }
254
+
255
+ // src/schema/index.ts
256
+ function defineCollection(opts) {
257
+ return opts;
258
+ }
259
+ function definePage(opts) {
260
+ return opts;
261
+ }
262
+ function definePageTemplate(opts) {
263
+ return opts;
264
+ }
265
+ function defineBlock(opts) {
266
+ return opts;
267
+ }
268
+ function definePlugin(opts) {
269
+ return opts;
270
+ }
271
+ function defineConfig(opts) {
272
+ return opts;
273
+ }
274
+
275
+ export { MediaFields, MEDIA_FIELD_NAMES, REPO_MEDIA_FIELD_NAMES, withMediaFields, MediaRef, mediaTransformUrl, resolveMediaRef, mediaRepoName, stageRepoMedia, PLUGIN_CAPABILITIES, deriveContributedCapabilities, assertPluginCapabilities, defineCollection, definePage, definePageTemplate, defineBlock, definePlugin, defineConfig };