sveltekit-admin 0.5.3 → 0.6.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 (39) hide show
  1. package/README.md +32 -1
  2. package/dist/index.d.ts +3 -0
  3. package/dist/index.js +1 -0
  4. package/dist/server/adapters/drizzle/dataAdapter.d.ts +11 -0
  5. package/dist/server/adapters/drizzle/dataAdapter.js +243 -0
  6. package/dist/server/adapters/drizzle/filterCompiler.d.ts +8 -0
  7. package/dist/server/adapters/drizzle/filterCompiler.js +61 -0
  8. package/dist/server/adapters/drizzle/index.d.ts +13 -0
  9. package/dist/server/adapters/drizzle/index.js +23 -0
  10. package/dist/server/adapters/drizzle/inspect.d.ts +23 -0
  11. package/dist/server/adapters/drizzle/inspect.js +220 -0
  12. package/dist/server/adapters/drizzle/introspector.d.ts +3 -0
  13. package/dist/server/adapters/drizzle/introspector.js +3 -0
  14. package/dist/server/adapters/filter.d.ts +12 -0
  15. package/dist/server/adapters/filter.js +72 -0
  16. package/dist/server/adapters/prisma/dataAdapter.d.ts +13 -0
  17. package/dist/server/adapters/prisma/dataAdapter.js +96 -0
  18. package/dist/server/adapters/prisma/filterCompiler.d.ts +9 -0
  19. package/dist/server/adapters/prisma/filterCompiler.js +42 -0
  20. package/dist/server/adapters/prisma/index.d.ts +28 -0
  21. package/dist/server/adapters/prisma/index.js +32 -0
  22. package/dist/server/adapters/prisma/introspector.d.ts +4 -0
  23. package/dist/server/adapters/prisma/introspector.js +9 -0
  24. package/dist/server/adapters/types.d.ts +79 -0
  25. package/dist/server/adapters/types.js +1 -0
  26. package/dist/server/data.d.ts +0 -8
  27. package/dist/server/data.js +0 -41
  28. package/dist/server/handler.d.ts +18 -2
  29. package/dist/server/handler.js +110 -84
  30. package/dist/server/introspection/parser.d.ts +4 -40
  31. package/dist/server/introspection/relations.d.ts +1 -1
  32. package/dist/server/introspection/relations.js +1 -1
  33. package/dist/server/query/listQuery.d.ts +6 -12
  34. package/dist/server/query/listQuery.js +31 -53
  35. package/dist/server/types/schema.d.ts +40 -0
  36. package/dist/server/types/schema.js +8 -0
  37. package/dist/server/views/Form.svelte +1 -1
  38. package/dist/server/views/RelationCheckboxes.svelte +1 -1
  39. package/package.json +23 -2
@@ -12,6 +12,7 @@
12
12
  * string) becomes the Prisma operator key.
13
13
  */
14
14
  import { isSensitiveFieldName } from '../introspection/parser.js';
15
+ import { normalizeScope } from '../adapters/filter.js';
15
16
  /** Max length accepted for the free-text search term. Longer input is truncated. */
16
17
  const MAX_SEARCH_LENGTH = 200;
17
18
  /**
@@ -351,92 +352,69 @@ export function parseListQuery(searchParams, model, enums, searchFields, filtera
351
352
  }
352
353
  function clauseOf(filter) {
353
354
  if (filter.op === 'gte' && filter.value && typeof filter.value === 'object' && 'gte' in filter.value) {
354
- // Date shortcut carrying both bounds (see parseOneFilter's DateTime branch).
355
+ // Date shortcut carrying both bounds (see parseOneFilter's DateTime branch)
356
+ // becomes two leaf clauses, re-merged by the Prisma filterCompiler.
355
357
  const range = filter.value;
356
- return { [filter.field]: { gte: range.gte, lt: range.lt } };
358
+ return [
359
+ { op: 'gte', field: filter.field, value: range.gte },
360
+ { op: 'lt', field: filter.field, value: range.lt }
361
+ ];
357
362
  }
358
363
  if (filter.op === 'isnull') {
359
- return { [filter.field]: filter.value ? { equals: null } : { not: null } };
364
+ return [{ op: filter.value ? 'isNull' : 'isNotNull', field: filter.field }];
360
365
  }
361
366
  if (filter.op === 'equals') {
362
- return { [filter.field]: filter.value };
367
+ return [{ op: 'eq', field: filter.field, value: filter.value }];
363
368
  }
364
- return { [filter.field]: { [filter.op]: filter.value } };
369
+ return [{ op: filter.op, field: filter.field, value: filter.value }];
365
370
  }
366
371
  /**
367
- * Compose the final Prisma `where`: `AND: [scope, ...filters, {OR: search}]`.
368
- * NEVER a spread a spread of `{...scope, ...filterWhere}` lets a filter
369
- * on the same field as the developer's scoping silently overwrite it
370
- * (docs/design §0.c, the exact IDOR the previous `?filter=` had). Two
371
- * clauses on the same field inside `AND` intersect; they never merge.
372
- *
373
- * Returns `undefined` when nothing is active, so the query shape sent to
374
- * Prisma is byte-for-byte identical to today's unfiltered call — no
375
- * regression on existing snapshots/assertions.
372
+ * Compose the final generic `Filter`: `and: [scope, ...filters, {or: search}]`.
373
+ * NEVER a spread. Flat `{ tenantId: 1 }` scopes become `eq` leaves via
374
+ * `normalizeScope`; nested Prisma where objects stay opaque for the Prisma
375
+ * compiler. Drizzle's compiler throws on those opaques.
376
376
  */
377
377
  export function buildWhere(query, scope, caseInsensitiveSearch, model) {
378
378
  const and = [];
379
- if (scope)
380
- and.push(scope);
379
+ const normalized = normalizeScope(scope);
380
+ if (normalized)
381
+ and.push(normalized);
381
382
  for (const f of query.filters)
382
- and.push(clauseOf(f));
383
+ and.push(...clauseOf(f));
383
384
  if (query.q && query.searchFields.length > 0) {
384
385
  const or = [];
385
386
  for (const fieldName of query.searchFields) {
386
387
  const field = model.fields.find((f) => f.name === fieldName);
387
- const clause = searchClauseFor(field, query.q, caseInsensitiveSearch);
388
+ const clause = searchClauseFor(field, fieldName, query.q);
388
389
  if (clause)
389
- or.push({ [fieldName]: clause });
390
+ or.push(clause);
390
391
  }
391
- // Never emit `{OR: []}` — in Prisma that matches nothing, which would
392
- // silently turn "no searchable field" (or "every clause omitted", §2.4)
393
- // into "empty result". A no-op search must add nothing to the where.
394
392
  if (or.length > 0)
395
- and.push({ OR: or });
393
+ and.push({ op: 'or', clauses: or });
396
394
  }
397
395
  if (and.length === 0)
398
396
  return undefined;
399
397
  if (and.length === 1)
400
398
  return and[0];
401
- return { AND: and };
399
+ return { op: 'and', clauses: and };
402
400
  }
403
401
  /**
404
- * The per-field-type clause for a `searchFields` entry (§2.4):
405
- * - String @id -> `equals` (a `contains` on a cuid/uuid can't use the
406
- * index and never makes semantic sense; §2.1 talks ONLY about the id
407
- * here an earlier version of this function over-generalized to
408
- * `@id || @unique`, which silently broke fragment search on the most
409
- * common real-world case: `email`/`slug` fields are `@unique` in
410
- * nearly every Prisma schema and are exactly what §2.3's "a title, an
411
- * email" example means by free-text search. `@unique` alone is NOT a
412
- * reason to switch to `equals` — only `@id` is).
413
- * - other String (including @unique) -> `contains` (+ `mode:
414
- * 'insensitive'` when the provider supports it).
415
- * - Int/BigInt/Float/Decimal -> `equals` if `q` coerces to that type,
416
- * otherwise the clause is OMITTED — never `contains` on a numeric
417
- * column, which Prisma rejects with a hard error (`Unknown argument
418
- * contains`), turning any legitimate `?q=` into a 500 (§10's known
419
- * trap, discovered via review — the original implementation searched
420
- * this exactly wrong).
421
- * - anything else (enum, Boolean, DateTime, relation, Json/Bytes):
422
- * omitted. `resolveSearchFields`'s auto heuristic never proposes these,
423
- * but explicit `searchFields` config isn't type-checked against §2.4 at
424
- * boot (only against `isFilterableFieldType`), so this is reached in
425
- * practice for a misconfigured field — degrading to "omitted" here
426
- * keeps the guarantee that a legitimate URL never 500s, without adding
427
- * a boot-time validation pass this design doc doesn't ask for.
402
+ * The per-field-type clause for a `searchFields` entry (§2.4) — same rules as
403
+ * before (String @id -> eq, other String -> contains, numeric -> eq if
404
+ * coercible else omitted, anything else omitted). `caseInsensitiveSearch` no
405
+ * longer lives here: the generic `Filter` doesn't carry a case-sensitivity
406
+ * flag, `filterCompiler.ts` decides whether to add `mode: 'insensitive'` from
407
+ * the same boolean at the handler.ts call site instead.
428
408
  */
429
- function searchClauseFor(field, q, caseInsensitiveSearch) {
409
+ function searchClauseFor(field, fieldName, q) {
430
410
  if (!field)
431
411
  return undefined;
432
412
  if (field.type === 'String') {
433
- if (field.isId)
434
- return { equals: q };
435
- return caseInsensitiveSearch ? { contains: q, mode: 'insensitive' } : { contains: q };
413
+ return field.isId ? { op: 'eq', field: fieldName, value: q } : { op: 'contains', field: fieldName, value: q };
436
414
  }
437
415
  if (['Int', 'BigInt', 'Float', 'Decimal'].includes(field.type)) {
438
416
  const coerced = coerceValue(field, 'equals', q);
439
- return coerced === undefined ? undefined : { equals: coerced };
417
+ return coerced === undefined ? undefined : { op: 'eq', field: fieldName, value: coerced };
440
418
  }
441
419
  return undefined;
442
420
  }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Generic schema shapes shared by every schema-source adapter (Prisma today,
3
+ * others later). Deliberately identical in shape to what `introspection/
4
+ * parser.ts` has always produced — this file is a rename of that shape, not
5
+ * a redesign of it. `PrismaSchema`/`PrismaModel`/`PrismaField` in parser.ts
6
+ * become aliases of these.
7
+ */
8
+ export interface Field {
9
+ name: string;
10
+ type: string;
11
+ isRequired: boolean;
12
+ isList: boolean;
13
+ isUnique: boolean;
14
+ isId: boolean;
15
+ isUpdatedAt: boolean;
16
+ isCreatedAt: boolean;
17
+ hasDefault: boolean;
18
+ defaultValue?: string;
19
+ /** true si `type` correspond à un `enum` déclaré dans le même schéma. */
20
+ isEnum?: boolean;
21
+ relation?: {
22
+ name?: string;
23
+ model: string;
24
+ fields?: string[];
25
+ references?: string[];
26
+ };
27
+ documentation?: string;
28
+ }
29
+ export interface Model {
30
+ name: string;
31
+ fields: Field[];
32
+ documentation?: string;
33
+ primaryKey?: string;
34
+ isPivotTable?: boolean;
35
+ }
36
+ export interface Schema {
37
+ models: Model[];
38
+ enums: Map<string, string[]>;
39
+ provider?: string;
40
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Generic schema shapes shared by every schema-source adapter (Prisma today,
3
+ * others later). Deliberately identical in shape to what `introspection/
4
+ * parser.ts` has always produced — this file is a rename of that shape, not
5
+ * a redesign of it. `PrismaSchema`/`PrismaModel`/`PrismaField` in parser.ts
6
+ * become aliases of these.
7
+ */
8
+ export {};
@@ -69,7 +69,7 @@
69
69
  ? [...model.relationGraph.edges.values()].filter(
70
70
  (e) =>
71
71
  e.model === model.name &&
72
- e.kind === 'm2m-implicit' &&
72
+ e.kind === 'm2m' &&
73
73
  !e.unsupported &&
74
74
  !hidden.includes(e.field) &&
75
75
  model.relationOptions?.has(`${e.model}.${e.field}`)
@@ -17,7 +17,7 @@
17
17
  </script>
18
18
 
19
19
  <!--
20
- Fieldset de checkboxes pour une arête m2m-implicite.
20
+ Fieldset de checkboxes pour une arête m2m.
21
21
 
22
22
  Nommage `__rel__<field>` pour les valeurs cochées et un hidden sentinelle
23
23
  `__rel_present__<field>` toujours émis : en HTML, zéro checkbox cochée
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sveltekit-admin",
3
- "version": "0.5.3",
3
+ "version": "0.6.0",
4
4
  "description": "Django-like admin panel for SvelteKit + Prisma",
5
5
  "type": "module",
6
6
  "svelte": "./dist/index.js",
@@ -10,6 +10,11 @@
10
10
  "types": "./dist/index.d.ts",
11
11
  "svelte": "./dist/index.js",
12
12
  "default": "./dist/index.js"
13
+ },
14
+ "./adapters/drizzle": {
15
+ "types": "./dist/server/adapters/drizzle/index.d.ts",
16
+ "svelte": "./dist/server/adapters/drizzle/index.js",
17
+ "default": "./dist/server/adapters/drizzle/index.js"
13
18
  }
14
19
  },
15
20
  "files": [
@@ -30,22 +35,38 @@
30
35
  "test:watch": "svelte-kit sync && npm run test:gen && vitest",
31
36
  "test:coverage": "svelte-kit sync && npm run test:gen && vitest run --coverage",
32
37
  "lint": "svelte-kit sync && eslint .",
33
- "format": "prettier --write ."
38
+ "format": "prettier --write .",
39
+ "changeset": "changeset",
40
+ "version-packages": "changeset version",
41
+ "release": "npm run package && changeset publish"
34
42
  },
35
43
  "peerDependencies": {
36
44
  "@prisma/client": ">=5.0.0",
37
45
  "@sveltejs/kit": ">=2.0.0",
46
+ "drizzle-orm": ">=0.32.0",
38
47
  "svelte": ">=5.0.0"
39
48
  },
49
+ "peerDependenciesMeta": {
50
+ "@prisma/client": {
51
+ "optional": true
52
+ },
53
+ "drizzle-orm": {
54
+ "optional": true
55
+ }
56
+ },
40
57
  "devDependencies": {
58
+ "@changesets/cli": "^3.0.0",
41
59
  "@eslint/js": "9",
42
60
  "@prisma/client": "^6.19.3",
43
61
  "@sveltejs/adapter-auto": "^7.0.1",
44
62
  "@sveltejs/kit": "^2.0.0",
45
63
  "@sveltejs/package": "^2.5.8",
46
64
  "@sveltejs/vite-plugin-svelte": "^4.0.0",
65
+ "@types/better-sqlite3": "^9.6.0",
47
66
  "@types/node": "^22.0.0",
48
67
  "@vitest/coverage-v8": "^3.2.7",
68
+ "better-sqlite3": "12.9.0",
69
+ "drizzle-orm": "^0.45.2",
49
70
  "eslint": "9",
50
71
  "eslint-config-prettier": "^10.1.8",
51
72
  "eslint-plugin-svelte": "^3.22.0",