@tinacms/graphql 3.0.0 → 4.0.1

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.
package/dist/index.js CHANGED
@@ -3058,7 +3058,7 @@ var assertCheckboxGroupIsList = (field) => {
3058
3058
  var package_default = {
3059
3059
  name: "@tinacms/graphql",
3060
3060
  type: "module",
3061
- version: "3.0.0",
3061
+ version: "4.0.1",
3062
3062
  main: "dist/index.js",
3063
3063
  module: "./dist/index.js",
3064
3064
  files: [
@@ -3101,7 +3101,7 @@ var package_default = {
3101
3101
  "gray-matter": "catalog:",
3102
3102
  "isomorphic-git": "catalog:",
3103
3103
  "js-sha1": "catalog:",
3104
- "js-yaml": "^3.15.1",
3104
+ "js-yaml": "^3.15.2",
3105
3105
  "jsonpath-plus": "catalog:",
3106
3106
  "many-level": "catalog:",
3107
3107
  micromatch: "catalog:",
@@ -3348,13 +3348,136 @@ import {
3348
3348
  } from "graphql";
3349
3349
 
3350
3350
  // src/resolver/index.ts
3351
- import path3 from "path";
3351
+ import path4 from "path";
3352
3352
  import { isValid } from "date-fns";
3353
3353
  import { JSONPath as JSONPath2 } from "jsonpath-plus";
3354
3354
 
3355
3355
  // src/mdx/index.ts
3356
3356
  import { parseMDX, serializeMDX } from "@tinacms/mdx";
3357
3357
 
3358
+ // src/references/visit.ts
3359
+ var MDX_SEGMENT = { mdx: true };
3360
+ var encodeLocator = (locator) => JSON.stringify(locator);
3361
+ var isRecord = (value) => typeof value === "object" && value !== null;
3362
+ var visitReferences = (data, collectable, tinaSchema, visit) => {
3363
+ const visitCollectable = (collectableHere, value, locator) => {
3364
+ if (!isRecord(value)) {
3365
+ return false;
3366
+ }
3367
+ let fields;
3368
+ try {
3369
+ fields = tinaSchema.getTemplateForData({
3370
+ data: value,
3371
+ collection: collectableHere
3372
+ })?.fields;
3373
+ } catch {
3374
+ return false;
3375
+ }
3376
+ let replaced = false;
3377
+ for (const field of fields ?? []) {
3378
+ replaced = visitField(field, value, locator) || replaced;
3379
+ }
3380
+ return replaced;
3381
+ };
3382
+ const visitField = (field, parent, locator) => {
3383
+ const key = field.type === "rich-text" && field.isBody && "$_body" in parent ? "$_body" : field.name;
3384
+ const value = parent[key];
3385
+ if (value === void 0 || value === null) {
3386
+ return false;
3387
+ }
3388
+ const at = [...locator, field.name];
3389
+ let replaced = false;
3390
+ if (field.type === "reference") {
3391
+ if (field.list && Array.isArray(value)) {
3392
+ for (const [index, item] of value.entries()) {
3393
+ if (typeof item !== "string" || item.length === 0) {
3394
+ continue;
3395
+ }
3396
+ visit({
3397
+ value: item,
3398
+ collections: field.collections ?? [],
3399
+ locator: [...at, index],
3400
+ replace: (next) => {
3401
+ value[index] = next;
3402
+ replaced = true;
3403
+ }
3404
+ });
3405
+ }
3406
+ return replaced;
3407
+ } else if (typeof value !== "string" || value.length === 0) {
3408
+ return false;
3409
+ }
3410
+ visit({
3411
+ value,
3412
+ collections: field.collections ?? [],
3413
+ locator: at,
3414
+ replace: (next) => {
3415
+ parent[field.name] = next;
3416
+ replaced = true;
3417
+ }
3418
+ });
3419
+ return replaced;
3420
+ } else if (field.type === "object") {
3421
+ if (field.list && Array.isArray(value)) {
3422
+ for (const [index, item] of value.entries()) {
3423
+ replaced = visitCollectable(field, item, [...at, index]) || replaced;
3424
+ }
3425
+ return replaced;
3426
+ }
3427
+ return visitCollectable(field, value, at);
3428
+ } else if (field.type === "rich-text") {
3429
+ return visitRichText(field, parent, key, at);
3430
+ }
3431
+ return false;
3432
+ };
3433
+ const visitRichText = (field, parent, key, at) => {
3434
+ const stored = parent[key];
3435
+ const storedIsText = typeof stored === "string";
3436
+ if (!storedIsText && !isRecord(stored)) {
3437
+ return false;
3438
+ }
3439
+ let tree;
3440
+ if (storedIsText) {
3441
+ try {
3442
+ tree = parseMDX(stored, field, (value) => value);
3443
+ } catch {
3444
+ return false;
3445
+ }
3446
+ } else {
3447
+ tree = stored;
3448
+ }
3449
+ const replaced = visitNodes2(tree?.children, field, [...at, MDX_SEGMENT]);
3450
+ if (replaced && storedIsText) {
3451
+ parent[key] = serializeMDX(tree, field, (value) => value);
3452
+ }
3453
+ return replaced;
3454
+ };
3455
+ const visitNodes2 = (nodes, field, at) => {
3456
+ if (!Array.isArray(nodes)) {
3457
+ return false;
3458
+ }
3459
+ const templates = field.templates ?? [];
3460
+ let replaced = false;
3461
+ for (const [index, node] of nodes.entries()) {
3462
+ if (!isRecord(node)) {
3463
+ continue;
3464
+ }
3465
+ const here = [...at, index];
3466
+ if (typeof node.name === "string" && (node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement") && isRecord(node.props)) {
3467
+ const template = templates.find(
3468
+ (candidate) => lastItem(candidate.namespace) === node.name
3469
+ );
3470
+ for (const templateField of template?.fields ?? []) {
3471
+ replaced = visitField(templateField, node.props, [...here, "props"]) || replaced;
3472
+ }
3473
+ }
3474
+ replaced = visitNodes2(node.children, field, [...here, "children"]) || replaced;
3475
+ }
3476
+ return replaced;
3477
+ };
3478
+ return visitCollectable(collectable, data, []);
3479
+ };
3480
+
3358
3481
  // src/resolver/index.ts
3359
3482
  import {
3360
3483
  ERR_ALREADY_EXISTS,
@@ -4431,54 +4554,41 @@ var makeIndexOpsForDocument = (filepath, collection, indexDefinitions, data, opT
4431
4554
  }
4432
4555
  return result;
4433
4556
  };
4434
- var makeRefOpsForDocument = (filepath, collection, references, data, opType, level) => {
4557
+ var makeRefOpsForDocument = (filepath, collectionName, tinaSchema, data, opType, level) => {
4435
4558
  const result = [];
4436
- if (collection) {
4437
- for (const [c, referencePaths] of Object.entries(references || {})) {
4438
- if (!referencePaths.length) {
4439
- continue;
4440
- }
4441
- const collectionSublevel = level.sublevel(c, SUBLEVEL_OPTIONS);
4442
- const refSublevel = collectionSublevel.sublevel(
4443
- REFS_COLLECTIONS_SORT_KEY,
4444
- SUBLEVEL_OPTIONS
4445
- );
4446
- const references2 = {};
4447
- for (const path9 of referencePaths) {
4448
- const ref = JSONPath({ path: path9, json: data });
4449
- if (!ref) {
4450
- continue;
4451
- }
4452
- if (Array.isArray(ref)) {
4453
- for (const r of ref) {
4454
- if (!r) {
4455
- continue;
4456
- }
4457
- if (references2[r]) {
4458
- references2[r].push(path9);
4459
- } else {
4460
- references2[r] = [path9];
4461
- }
4462
- }
4463
- } else {
4464
- if (references2[ref]) {
4465
- references2[ref].push(path9);
4466
- } else {
4467
- references2[ref] = [path9];
4468
- }
4469
- }
4470
- }
4471
- for (const ref of Object.keys(references2)) {
4472
- for (const path9 of references2[ref]) {
4473
- result.push({
4474
- type: opType,
4475
- key: `${ref}${INDEX_KEY_FIELD_SEPARATOR}${path9}${INDEX_KEY_FIELD_SEPARATOR}${filepath}`,
4476
- sublevel: refSublevel,
4477
- value: opType === "put" ? {} : void 0
4478
- });
4479
- }
4559
+ if (!collectionName || !tinaSchema) {
4560
+ return result;
4561
+ }
4562
+ let collection;
4563
+ try {
4564
+ collection = tinaSchema.getCollection(collectionName);
4565
+ } catch {
4566
+ return result;
4567
+ }
4568
+ const byCollection = /* @__PURE__ */ new Map();
4569
+ visitReferences(
4570
+ data,
4571
+ collection,
4572
+ tinaSchema,
4573
+ ({ value, collections, locator }) => {
4574
+ const encoded = encodeLocator(locator);
4575
+ for (const target of collections) {
4576
+ const entries = byCollection.get(target) ?? [];
4577
+ entries.push({ ref: value, locator: encoded });
4578
+ byCollection.set(target, entries);
4480
4579
  }
4481
4580
  }
4581
+ );
4582
+ for (const [target, entries] of byCollection) {
4583
+ const refSublevel = level.sublevel(target, SUBLEVEL_OPTIONS).sublevel(REFS_COLLECTIONS_SORT_KEY, SUBLEVEL_OPTIONS);
4584
+ for (const { ref, locator } of entries) {
4585
+ result.push({
4586
+ type: opType,
4587
+ key: `${ref}${INDEX_KEY_FIELD_SEPARATOR}${locator}${INDEX_KEY_FIELD_SEPARATOR}${filepath}`,
4588
+ sublevel: refSublevel,
4589
+ value: opType === "put" ? {} : void 0
4590
+ });
4591
+ }
4482
4592
  }
4483
4593
  return result;
4484
4594
  };
@@ -4683,6 +4793,170 @@ var collectConditionsForField = (fieldName, field, filterNode, pathExpression, c
4683
4793
  }
4684
4794
  };
4685
4795
 
4796
+ // src/resolver/auth-fields.ts
4797
+ import { randomBytes } from "crypto";
4798
+ import path3 from "path";
4799
+ async function getUserDocumentContext(tinaSchema, resolver) {
4800
+ const collection = tinaSchema.getCollections().find((c) => c.isAuthCollection);
4801
+ if (!collection) {
4802
+ throw new Error("Auth collection not found");
4803
+ }
4804
+ const userFields = mapUserFields(collection, ["_rawData"]);
4805
+ if (!userFields.length) {
4806
+ throw new Error(`No user field found in collection ${collection.name}`);
4807
+ }
4808
+ if (userFields.length > 1) {
4809
+ throw new Error(
4810
+ `Multiple user fields found in collection ${collection.name}`
4811
+ );
4812
+ }
4813
+ const userField = userFields[0];
4814
+ const relativePath = "index.json";
4815
+ const realPath = path3.join(collection.path, relativePath);
4816
+ const userDoc = await resolver.getDocument(realPath);
4817
+ const users = get(userDoc, userField.path);
4818
+ if (!users) {
4819
+ throw new Error("No users found");
4820
+ }
4821
+ return { collection, userField, users, userDoc, relativePath };
4822
+ }
4823
+ function findUserInCollection(users, userField, userSub) {
4824
+ const { idFieldName } = userField;
4825
+ if (!idFieldName) {
4826
+ throw new Error("No uid field found on user field");
4827
+ }
4828
+ return users.find((u) => u[idFieldName] === userSub) || null;
4829
+ }
4830
+ function withoutPasswordValue(user, userField) {
4831
+ const { passwordFieldName } = userField;
4832
+ if (!passwordFieldName) {
4833
+ return user;
4834
+ }
4835
+ return {
4836
+ ...user,
4837
+ [passwordFieldName]: {
4838
+ passwordChangeRequired: user[passwordFieldName]?.passwordChangeRequired ?? false
4839
+ }
4840
+ };
4841
+ }
4842
+ var dummyPasswordHash;
4843
+ var warnedUnflaggedAuthenticate = false;
4844
+ var getDummyPasswordHash = () => {
4845
+ dummyPasswordHash ??= generatePasswordHash({
4846
+ password: randomBytes(32).toString("hex")
4847
+ });
4848
+ return dummyPasswordHash;
4849
+ };
4850
+ function assertNewUsersHavePasswords(collection, newBody, existingData) {
4851
+ const userFields = mapUserFields(collection);
4852
+ if (userFields.length !== 1) {
4853
+ return;
4854
+ }
4855
+ const [{ path: usersPath, idFieldName, passwordFieldName }] = userFields;
4856
+ const users = get(newBody, usersPath);
4857
+ if (!Array.isArray(users) || !idFieldName || !passwordFieldName) {
4858
+ return;
4859
+ }
4860
+ const storedUsers = get(existingData, usersPath);
4861
+ const storedIds = new Set(
4862
+ (Array.isArray(storedUsers) ? storedUsers : []).map((u) => u?.[idFieldName])
4863
+ );
4864
+ const withoutPassword = users.filter(
4865
+ (u) => !storedIds.has(u?.[idFieldName]) && !u?.[passwordFieldName]?.["value"]
4866
+ ).map((u) => u?.[idFieldName]);
4867
+ if (withoutPassword.length) {
4868
+ throw new Error(`New users need a password: ${withoutPassword.join(", ")}`);
4869
+ }
4870
+ }
4871
+ async function handleAuthenticate({
4872
+ tinaSchema,
4873
+ resolver,
4874
+ sub,
4875
+ password,
4876
+ isSignIn
4877
+ }) {
4878
+ if (!isSignIn) {
4879
+ if (!warnedUnflaggedAuthenticate) {
4880
+ warnedUnflaggedAuthenticate = true;
4881
+ console.warn(
4882
+ "TinaCMS: authenticate was called without isSignIn, so it returned null. If sign-in is failing, run `tinacms build` with a @tinacms/cli that matches @tinacms/datalayer to regenerate the database client."
4883
+ );
4884
+ }
4885
+ return null;
4886
+ }
4887
+ const { userField, users } = await getUserDocumentContext(
4888
+ tinaSchema,
4889
+ resolver
4890
+ );
4891
+ const user = sub ? findUserInCollection(users, userField, sub) : null;
4892
+ const { passwordFieldName } = userField;
4893
+ const saltedHash = user ? get(user, [passwordFieldName || "", "value"]) : void 0;
4894
+ if (!saltedHash) {
4895
+ await checkPasswordHash({
4896
+ saltedHash: await getDummyPasswordHash(),
4897
+ password: password ?? ""
4898
+ });
4899
+ return null;
4900
+ }
4901
+ const matches = await checkPasswordHash({
4902
+ saltedHash,
4903
+ password
4904
+ });
4905
+ return matches ? withoutPasswordValue(user, userField) : null;
4906
+ }
4907
+ async function handleAuthorize({
4908
+ tinaSchema,
4909
+ resolver,
4910
+ sub,
4911
+ ctxUser
4912
+ }) {
4913
+ const userSub = sub || ctxUser?.sub;
4914
+ const { userField, users } = await getUserDocumentContext(
4915
+ tinaSchema,
4916
+ resolver
4917
+ );
4918
+ const user = findUserInCollection(users, userField, userSub);
4919
+ return user ? withoutPasswordValue(user, userField) : null;
4920
+ }
4921
+ async function handleUpdatePassword({
4922
+ tinaSchema,
4923
+ resolver,
4924
+ password,
4925
+ ctxUser
4926
+ }) {
4927
+ if (!ctxUser?.sub) {
4928
+ throw new Error("Not authorized");
4929
+ }
4930
+ if (!password) {
4931
+ throw new Error("No password provided");
4932
+ }
4933
+ const { userField, users } = await getUserDocumentContext(
4934
+ tinaSchema,
4935
+ resolver
4936
+ );
4937
+ const { idFieldName, passwordFieldName } = userField;
4938
+ if (!passwordFieldName) {
4939
+ throw new Error("No password field found on user field");
4940
+ }
4941
+ if (!users.find((u) => u[idFieldName] === ctxUser.sub)) {
4942
+ throw new Error("Not authorized");
4943
+ }
4944
+ const passwordHash = await generatePasswordHash({ password });
4945
+ await resolver.updateAuthDocumentInternal((rawData) => {
4946
+ const storedUsers = get(rawData, userField.path.slice(1));
4947
+ const user = Array.isArray(storedUsers) ? storedUsers.find((u) => u[idFieldName] === ctxUser.sub) : void 0;
4948
+ if (!user) {
4949
+ throw new Error("Not authorized");
4950
+ }
4951
+ user[passwordFieldName] = {
4952
+ value: passwordHash,
4953
+ passwordChangeRequired: false
4954
+ };
4955
+ return rawData;
4956
+ });
4957
+ return true;
4958
+ }
4959
+
4686
4960
  // src/resolver/media-utils.ts
4687
4961
  var resolveMediaCloudToRelative = (value, config = { useRelativeMedia: true }, schema) => {
4688
4962
  if (config && value) {
@@ -4764,6 +5038,7 @@ var hasTinaMediaConfig = (schema) => {
4764
5038
  };
4765
5039
 
4766
5040
  // src/resolver/index.ts
5041
+ var authCollectionWrites = /* @__PURE__ */ new WeakMap();
4767
5042
  var createResolver = (args) => {
4768
5043
  return new Resolver(args);
4769
5044
  };
@@ -4903,7 +5178,7 @@ var transformDocumentIntoPayload = async (fullPath, rawData, tinaSchema, config,
4903
5178
  base: basename,
4904
5179
  ext: extension,
4905
5180
  name: filename
4906
- } = path3.parse(normalizedPath);
5181
+ } = path4.parse(normalizedPath);
4907
5182
  const relativePath = normalizedPath.replace(collection.path, "").replace(/^\/|\/$/g, "");
4908
5183
  const breadcrumbs = relativePath.replace(extension, "").split("/");
4909
5184
  const data = {
@@ -4965,34 +5240,6 @@ var transformDocumentIntoPayload = async (fullPath, rawData, tinaSchema, config,
4965
5240
  throw e;
4966
5241
  }
4967
5242
  };
4968
- var updateObjectWithJsonPath = (obj, path9, oldValue, newValue) => {
4969
- let updated = false;
4970
- if (!path9.includes(".") && !path9.includes("[")) {
4971
- if (path9 in obj && obj[path9] === oldValue) {
4972
- obj[path9] = newValue;
4973
- updated = true;
4974
- }
4975
- return { object: obj, updated };
4976
- }
4977
- const parentPath = path9.replace(/\.[^.\[\]]+$/, "");
4978
- const keyToUpdate = path9.match(/[^.\[\]]+$/)[0];
4979
- const parents = JSONPath2({
4980
- path: parentPath,
4981
- json: obj,
4982
- resultType: "value"
4983
- });
4984
- if (parents.length > 0) {
4985
- parents.forEach((parent) => {
4986
- if (parent && typeof parent === "object" && keyToUpdate in parent) {
4987
- if (parent[keyToUpdate] === oldValue) {
4988
- parent[keyToUpdate] = newValue;
4989
- updated = true;
4990
- }
4991
- }
4992
- });
4993
- }
4994
- return { object: obj, updated };
4995
- };
4996
5243
  var Resolver = class _Resolver {
4997
5244
  constructor(init) {
4998
5245
  this.init = init;
@@ -5000,12 +5247,14 @@ var Resolver = class _Resolver {
5000
5247
  this.database = init.database;
5001
5248
  this.tinaSchema = init.tinaSchema;
5002
5249
  this.isAudit = init.isAudit;
5250
+ this.ctxUser = init.ctxUser;
5003
5251
  }
5004
5252
  init;
5005
5253
  config;
5006
5254
  database;
5007
5255
  tinaSchema;
5008
5256
  isAudit;
5257
+ ctxUser;
5009
5258
  resolveCollection = async (_args, collectionName, hasDocuments) => {
5010
5259
  const collection = this.tinaSchema.getCollection(collectionName);
5011
5260
  const extraFields = {};
@@ -5182,6 +5431,11 @@ var Resolver = class _Resolver {
5182
5431
  collectionName,
5183
5432
  relativePath
5184
5433
  );
5434
+ await this.assertAuthCollectionWrite({
5435
+ collection,
5436
+ realPath,
5437
+ op: "addPending"
5438
+ });
5185
5439
  const alreadyExists = await this.database.documentExists(realPath);
5186
5440
  if (alreadyExists) {
5187
5441
  throw new Error(
@@ -5297,19 +5551,19 @@ var Resolver = class _Resolver {
5297
5551
  if (fullPath.includes("\0")) {
5298
5552
  throw new Error("Invalid path: null bytes are not allowed");
5299
5553
  }
5300
- const normalizedPath = path3.normalize(fullPath.replace(/\\/g, "/"));
5301
- const normalizedCollectionPath = path3.normalize(collection.path);
5302
- const relative = path3.relative(normalizedCollectionPath, normalizedPath);
5554
+ const normalizedPath = path4.normalize(fullPath.replace(/\\/g, "/"));
5555
+ const normalizedCollectionPath = path4.normalize(collection.path);
5556
+ const relative = path4.relative(normalizedCollectionPath, normalizedPath);
5303
5557
  if (relative.startsWith("..")) {
5304
5558
  throw new Error(`Invalid path: path escapes the collection directory`);
5305
5559
  }
5306
- if (path3.isAbsolute(relative)) {
5560
+ if (path4.isAbsolute(relative)) {
5307
5561
  throw new Error(`Invalid path: absolute paths are not allowed`);
5308
5562
  }
5309
5563
  if (relativePath) {
5310
5564
  _Resolver.validateRelativePath(relativePath);
5311
5565
  const collectionFormat = collection.format || "md";
5312
- const fileExtension = path3.extname(relativePath).toLowerCase().slice(1);
5566
+ const fileExtension = path4.extname(relativePath).toLowerCase().slice(1);
5313
5567
  if (fileExtension !== collectionFormat) {
5314
5568
  throw new Error(
5315
5569
  `Invalid file extension: expected '.${collectionFormat}' but got '.${fileExtension}'`
@@ -5317,6 +5571,81 @@ var Resolver = class _Resolver {
5317
5571
  }
5318
5572
  }
5319
5573
  };
5574
+ assertAuthCollectionWrite = async ({
5575
+ collection,
5576
+ realPath,
5577
+ op
5578
+ }) => {
5579
+ const authCollection = this.tinaSchema.getCollections().find((c) => c.isAuthCollection);
5580
+ if (!authCollection) {
5581
+ return;
5582
+ }
5583
+ const normalizedPath = normalizePath2(realPath);
5584
+ if (collection.name !== authCollection.name && !this.isStoredInCollection(normalizedPath, authCollection)) {
5585
+ return;
5586
+ }
5587
+ const { admins, allowUnauthenticatedWrites } = this.database.authCollection ?? { admins: [], allowUnauthenticatedWrites: true };
5588
+ if (this.ctxUser === void 0 && allowUnauthenticatedWrites) {
5589
+ return;
5590
+ }
5591
+ const sub = this.ctxUser?.sub;
5592
+ const userStorePath = normalizePath2(
5593
+ path4.join(authCollection.path, "index.json")
5594
+ );
5595
+ if (op === "update" && normalizedPath === userStorePath && typeof sub === "string" && sub !== "" && admins.includes(sub) && await this.isStoredUser(sub)) {
5596
+ return;
5597
+ }
5598
+ throw new Error("Not authorized");
5599
+ };
5600
+ assertReferenceUpdates = async (realPath, collection) => {
5601
+ if (!this.tinaSchema.getCollections().some((c) => c.isAuthCollection)) {
5602
+ return;
5603
+ }
5604
+ const collRefs = await this.findReferences(realPath, collection);
5605
+ for (const docsWithRefs of Object.values(collRefs)) {
5606
+ for (const pathToDocWithRef of Object.keys(docsWithRefs)) {
5607
+ await this.assertAuthCollectionWrite({
5608
+ collection: this.tinaSchema.getCollectionByFullPath(pathToDocWithRef),
5609
+ realPath: pathToDocWithRef,
5610
+ op: "update"
5611
+ });
5612
+ }
5613
+ }
5614
+ };
5615
+ isAuthCollectionPath = (realPath) => {
5616
+ const authCollection = this.tinaSchema.getCollections().find((c) => c.isAuthCollection);
5617
+ return !!authCollection && this.isStoredInCollection(normalizePath2(realPath), authCollection);
5618
+ };
5619
+ withAuthCollectionLock = (realPath, write) => {
5620
+ if (!this.isAuthCollectionPath(realPath)) {
5621
+ return write();
5622
+ }
5623
+ const previous = authCollectionWrites.get(this.database);
5624
+ const current = previous ? previous.then(write, write) : write();
5625
+ authCollectionWrites.set(
5626
+ this.database,
5627
+ current.catch(() => void 0)
5628
+ );
5629
+ return current;
5630
+ };
5631
+ isStoredInCollection = (realPath, collection) => {
5632
+ try {
5633
+ return this.tinaSchema.getCollectionByFullPath(realPath)?.name === collection.name;
5634
+ } catch {
5635
+ return false;
5636
+ }
5637
+ };
5638
+ isStoredUser = async (sub) => {
5639
+ try {
5640
+ const { userField, users } = await getUserDocumentContext(
5641
+ this.tinaSchema,
5642
+ this
5643
+ );
5644
+ return Array.isArray(users) && !!findUserInCollection(users, userField, sub);
5645
+ } catch {
5646
+ return false;
5647
+ }
5648
+ };
5320
5649
  /**
5321
5650
  * Helper method to get collection and construct validated path.
5322
5651
  * This encapsulates the common pattern of getting a collection, joining paths,
@@ -5337,7 +5666,7 @@ var Resolver = class _Resolver {
5337
5666
  if (options?.extraSegments) {
5338
5667
  pathSegments.push(...options.extraSegments.map(_Resolver.sanitizePath));
5339
5668
  }
5340
- const realPath = normalizePath2(path3.join(...pathSegments));
5669
+ const realPath = normalizePath2(path4.join(...pathSegments));
5341
5670
  const shouldValidateExtension = options?.validateExtension !== false;
5342
5671
  this.validatePath(
5343
5672
  realPath,
@@ -5371,12 +5700,17 @@ var Resolver = class _Resolver {
5371
5700
  }) => {
5372
5701
  const collection = this.getCollectionWithName(collectionName);
5373
5702
  _Resolver.validateRelativePath(relativePath);
5374
- const realPath = path3.join(
5703
+ const realPath = path4.join(
5375
5704
  collection.path,
5376
5705
  relativePath,
5377
5706
  `.gitkeep.${collection.format || "md"}`
5378
5707
  );
5379
5708
  this.validatePath(realPath, collection);
5709
+ await this.assertAuthCollectionWrite({
5710
+ collection,
5711
+ realPath,
5712
+ op: "createFolder"
5713
+ });
5380
5714
  const alreadyExists = await this.database.documentExists(realPath);
5381
5715
  if (alreadyExists) {
5382
5716
  throw new Error(
@@ -5399,6 +5733,11 @@ var Resolver = class _Resolver {
5399
5733
  collectionName,
5400
5734
  relativePath
5401
5735
  );
5736
+ await this.assertAuthCollectionWrite({
5737
+ collection,
5738
+ realPath,
5739
+ op: "create"
5740
+ });
5402
5741
  const alreadyExists = await this.database.documentExists(realPath);
5403
5742
  if (alreadyExists) {
5404
5743
  throw new Error(
@@ -5409,7 +5748,17 @@ var Resolver = class _Resolver {
5409
5748
  await this.database.put(realPath, params, collection.name);
5410
5749
  return this.getDocument(realPath);
5411
5750
  };
5412
- resolveUpdateDocument = async ({
5751
+ resolveUpdateDocument = async (args) => {
5752
+ const { realPath } = this.getValidatedPath(
5753
+ args.collectionName,
5754
+ args.relativePath
5755
+ );
5756
+ return this.withAuthCollectionLock(
5757
+ realPath,
5758
+ () => this.updateDocument(args)
5759
+ );
5760
+ };
5761
+ updateDocument = async ({
5413
5762
  collectionName,
5414
5763
  relativePath,
5415
5764
  newRelativePath,
@@ -5419,6 +5768,13 @@ var Resolver = class _Resolver {
5419
5768
  collectionName,
5420
5769
  relativePath
5421
5770
  );
5771
+ if (!newRelativePath) {
5772
+ await this.assertAuthCollectionWrite({
5773
+ collection,
5774
+ realPath,
5775
+ op: "update"
5776
+ });
5777
+ }
5422
5778
  const alreadyExists = await this.database.documentExists(realPath);
5423
5779
  if (!alreadyExists) {
5424
5780
  throw new Error(`Unable to update document, ${realPath} does not exist`);
@@ -5438,45 +5794,33 @@ var Resolver = class _Resolver {
5438
5794
  `Unable to rename document, ${newRealPath} ${ERR_ALREADY_EXISTS}`
5439
5795
  );
5440
5796
  }
5797
+ await this.assertAuthCollectionWrite({
5798
+ collection,
5799
+ realPath,
5800
+ op: "rename"
5801
+ });
5802
+ await this.assertAuthCollectionWrite({
5803
+ collection,
5804
+ realPath: newRealPath,
5805
+ op: "rename"
5806
+ });
5807
+ await this.assertReferenceUpdates(realPath, collection);
5441
5808
  await this.database.put(newRealPath, doc._rawData, collection.name);
5442
5809
  await this.deleteDocument(realPath);
5443
- const collRefs = await this.findReferences(realPath, collection);
5444
- for (const [_collection, docsWithRefs] of Object.entries(collRefs)) {
5445
- for (const [pathToDocWithRef, referencePaths] of Object.entries(
5446
- docsWithRefs
5447
- )) {
5448
- let docWithRef = await this.getRaw(pathToDocWithRef);
5449
- let hasUpdate = false;
5450
- for (const path9 of referencePaths) {
5451
- const { object: object2, updated } = updateObjectWithJsonPath(
5452
- docWithRef,
5453
- path9,
5454
- realPath,
5455
- newRealPath
5456
- );
5457
- docWithRef = object2;
5458
- hasUpdate = updated || hasUpdate;
5459
- }
5460
- if (hasUpdate) {
5461
- const collectionWithRef = this.tinaSchema.getCollectionByFullPath(pathToDocWithRef);
5462
- if (!collectionWithRef) {
5463
- throw new Error(
5464
- `Unable to find collection for ${pathToDocWithRef}`
5465
- );
5466
- }
5467
- await this.database.put(
5468
- pathToDocWithRef,
5469
- docWithRef,
5470
- collectionWithRef.name
5471
- );
5472
- }
5473
- }
5474
- }
5810
+ await this.rewriteReferences(
5811
+ realPath,
5812
+ newRealPath,
5813
+ collection,
5814
+ this.isAuthCollectionPath(realPath)
5815
+ );
5475
5816
  return this.getDocument(newRealPath);
5476
5817
  }
5477
5818
  if (!newBody) {
5478
5819
  throw new Error("Body not provided for updated document.");
5479
5820
  }
5821
+ if (collection.isAuthCollection) {
5822
+ assertNewUsersHavePasswords(collection, newBody, doc?._rawData);
5823
+ }
5480
5824
  const params = await this.buildObjectMutations(
5481
5825
  newBody,
5482
5826
  collection,
@@ -5493,6 +5837,21 @@ var Resolver = class _Resolver {
5493
5837
  );
5494
5838
  return this.getDocument(realPath);
5495
5839
  };
5840
+ /**
5841
+ * Rewrites the auth collection's user store without the admin check, so the
5842
+ * caller must check the request user.
5843
+ */
5844
+ updateAuthDocumentInternal = async (update) => {
5845
+ const collection = this.tinaSchema.getCollections().find((c) => c.isAuthCollection);
5846
+ if (!collection) {
5847
+ throw new Error("Auth collection not found");
5848
+ }
5849
+ const { realPath } = this.getValidatedPath(collection.name, "index.json");
5850
+ await this.withAuthCollectionLock(realPath, async () => {
5851
+ const rawData = await this.getRaw(realPath);
5852
+ await this.database.put(realPath, update(rawData), collection.name);
5853
+ });
5854
+ };
5496
5855
  resolveDeleteDocument = async ({
5497
5856
  collectionName,
5498
5857
  relativePath
@@ -5505,41 +5864,19 @@ var Resolver = class _Resolver {
5505
5864
  if (!alreadyExists) {
5506
5865
  throw new Error(`Unable to delete document, ${realPath} does not exist`);
5507
5866
  }
5867
+ await this.assertAuthCollectionWrite({
5868
+ collection,
5869
+ realPath,
5870
+ op: "delete"
5871
+ });
5872
+ const hasReferences = await this.hasReferences(realPath, collection);
5873
+ if (hasReferences) {
5874
+ await this.assertReferenceUpdates(realPath, collection);
5875
+ }
5508
5876
  const doc = await this.getDocument(realPath);
5509
5877
  await this.deleteDocument(realPath);
5510
- if (await this.hasReferences(realPath, collection)) {
5511
- const collRefs = await this.findReferences(realPath, collection);
5512
- for (const [_collection, docsWithRefs] of Object.entries(collRefs)) {
5513
- for (const [pathToDocWithRef, referencePaths] of Object.entries(
5514
- docsWithRefs
5515
- )) {
5516
- let refDoc = await this.getRaw(pathToDocWithRef);
5517
- let hasUpdate = false;
5518
- for (const path9 of referencePaths) {
5519
- const { object: object2, updated } = updateObjectWithJsonPath(
5520
- refDoc,
5521
- path9,
5522
- realPath,
5523
- null
5524
- );
5525
- refDoc = object2;
5526
- hasUpdate = updated || hasUpdate;
5527
- }
5528
- if (hasUpdate) {
5529
- const collectionWithRef = this.tinaSchema.getCollectionByFullPath(pathToDocWithRef);
5530
- if (!collectionWithRef) {
5531
- throw new Error(
5532
- `Unable to find collection for ${pathToDocWithRef}`
5533
- );
5534
- }
5535
- await this.database.put(
5536
- pathToDocWithRef,
5537
- refDoc,
5538
- collectionWithRef.name
5539
- );
5540
- }
5541
- }
5542
- }
5878
+ if (hasReferences) {
5879
+ await this.rewriteReferences(realPath, null, collection, false);
5543
5880
  }
5544
5881
  return doc;
5545
5882
  };
@@ -5573,10 +5910,15 @@ var Resolver = class _Resolver {
5573
5910
  if (isAddPendingDocument === true) {
5574
5911
  return this.resolveAddPendingDocument({
5575
5912
  collectionName: collection.name,
5576
- relativePath: path3.relative(collection.path, realPath),
5913
+ relativePath: path4.relative(collection.path, realPath),
5577
5914
  templateName: args.template
5578
5915
  });
5579
5916
  }
5917
+ await this.assertAuthCollectionWrite({
5918
+ collection,
5919
+ realPath,
5920
+ op: "create"
5921
+ });
5580
5922
  const params = await this.buildObjectMutations(
5581
5923
  // @ts-ignore
5582
5924
  args.params[collection.name],
@@ -5595,6 +5937,11 @@ var Resolver = class _Resolver {
5595
5937
  isAddPendingDocument,
5596
5938
  isCollectionSpecific
5597
5939
  }) => {
5940
+ await this.assertAuthCollectionWrite({
5941
+ collection,
5942
+ realPath,
5943
+ op: "update"
5944
+ });
5598
5945
  const doc = await this.getDocument(realPath);
5599
5946
  const oldDoc = this.resolveLegacyValues(doc?._rawData || {}, collection);
5600
5947
  if (isAddPendingDocument === true) {
@@ -5887,6 +6234,47 @@ var Resolver = class _Resolver {
5887
6234
  }
5888
6235
  };
5889
6236
  };
6237
+ /**
6238
+ * Repoints every reference to `from` at `to`, or clears it when `to` is null.
6239
+ *
6240
+ * Each referring document is re-walked rather than addressed by the locators the index stored,
6241
+ * since a stored path cannot address a reference inside rich text. Only a document the walk
6242
+ * changed is written back.
6243
+ */
6244
+ rewriteReferences = async (from, to, collection, holdsLock) => {
6245
+ const collRefs = await this.findReferences(from, collection);
6246
+ for (const docsWithRefs of Object.values(collRefs)) {
6247
+ for (const pathToDocWithRef of Object.keys(docsWithRefs)) {
6248
+ const rewrite = async () => {
6249
+ const collectionWithRef = this.tinaSchema.getCollectionByFullPath(pathToDocWithRef);
6250
+ if (!collectionWithRef) {
6251
+ throw new Error(
6252
+ `Unable to find collection for ${pathToDocWithRef}`
6253
+ );
6254
+ }
6255
+ const docWithRef = await this.getRaw(pathToDocWithRef);
6256
+ const changed = visitReferences(
6257
+ docWithRef,
6258
+ collectionWithRef,
6259
+ this.tinaSchema,
6260
+ (reference) => {
6261
+ if (reference.value === from) {
6262
+ reference.replace(to);
6263
+ }
6264
+ }
6265
+ );
6266
+ if (changed) {
6267
+ await this.database.put(
6268
+ pathToDocWithRef,
6269
+ docWithRef,
6270
+ collectionWithRef.name
6271
+ );
6272
+ }
6273
+ };
6274
+ await (holdsLock ? rewrite() : this.withAuthCollectionLock(pathToDocWithRef, rewrite));
6275
+ }
6276
+ }
6277
+ };
5890
6278
  /**
5891
6279
  * Checks if a document has references to it
5892
6280
  * @param id The id of the document to check for references
@@ -5897,6 +6285,8 @@ var Resolver = class _Resolver {
5897
6285
  let count = 0;
5898
6286
  await this.database.query(
5899
6287
  {
6288
+ // One key answers a boolean; the default page is 50.
6289
+ first: 1,
5900
6290
  collection: c.name,
5901
6291
  filterChain: makeFilterChain({
5902
6292
  conditions: [
@@ -5932,6 +6322,9 @@ var Resolver = class _Resolver {
5932
6322
  const references = {};
5933
6323
  await this.database.query(
5934
6324
  {
6325
+ // Every reference, not the default page of 50, which otherwise caps how many a rename or
6326
+ // delete updates.
6327
+ first: -1,
5935
6328
  collection: c.name,
5936
6329
  filterChain: makeFilterChain({
5937
6330
  conditions: [
@@ -6101,130 +6494,6 @@ var resolveDateInput = (value) => {
6101
6494
  return date;
6102
6495
  };
6103
6496
 
6104
- // src/resolver/auth-fields.ts
6105
- import path4 from "path";
6106
- import { set } from "es-toolkit/compat";
6107
- async function getUserDocumentContext(tinaSchema, resolver) {
6108
- const collection = tinaSchema.getCollections().find((c) => c.isAuthCollection);
6109
- if (!collection) {
6110
- throw new Error("Auth collection not found");
6111
- }
6112
- const userFields = mapUserFields(collection, ["_rawData"]);
6113
- if (!userFields.length) {
6114
- throw new Error(`No user field found in collection ${collection.name}`);
6115
- }
6116
- if (userFields.length > 1) {
6117
- throw new Error(
6118
- `Multiple user fields found in collection ${collection.name}`
6119
- );
6120
- }
6121
- const userField = userFields[0];
6122
- const relativePath = "index.json";
6123
- const realPath = path4.join(collection.path, relativePath);
6124
- const userDoc = await resolver.getDocument(realPath);
6125
- const users = get(userDoc, userField.path);
6126
- if (!users) {
6127
- throw new Error("No users found");
6128
- }
6129
- return { collection, userField, users, userDoc, relativePath };
6130
- }
6131
- function findUserInCollection(users, userField, userSub) {
6132
- const { idFieldName } = userField;
6133
- if (!idFieldName) {
6134
- throw new Error("No uid field found on user field");
6135
- }
6136
- return users.find((u) => u[idFieldName] === userSub) || null;
6137
- }
6138
- async function handleAuthenticate({
6139
- tinaSchema,
6140
- resolver,
6141
- sub,
6142
- password,
6143
- ctxUser
6144
- }) {
6145
- const userSub = sub || ctxUser?.sub;
6146
- const { userField, users } = await getUserDocumentContext(
6147
- tinaSchema,
6148
- resolver
6149
- );
6150
- const user = findUserInCollection(users, userField, userSub);
6151
- if (!user) {
6152
- return null;
6153
- }
6154
- const { passwordFieldName } = userField;
6155
- const saltedHash = get(user, [passwordFieldName || "", "value"]);
6156
- if (!saltedHash) {
6157
- throw new Error("No password field found on user field");
6158
- }
6159
- const matches = await checkPasswordHash({
6160
- saltedHash,
6161
- password
6162
- });
6163
- return matches ? user : null;
6164
- }
6165
- async function handleAuthorize({
6166
- tinaSchema,
6167
- resolver,
6168
- sub,
6169
- ctxUser
6170
- }) {
6171
- const userSub = sub || ctxUser?.sub;
6172
- const { userField, users } = await getUserDocumentContext(
6173
- tinaSchema,
6174
- resolver
6175
- );
6176
- const user = findUserInCollection(users, userField, userSub);
6177
- return user ? user : null;
6178
- }
6179
- async function handleUpdatePassword({
6180
- tinaSchema,
6181
- resolver,
6182
- password,
6183
- ctxUser
6184
- }) {
6185
- if (!ctxUser?.sub) {
6186
- throw new Error("Not authorized");
6187
- }
6188
- if (!password) {
6189
- throw new Error("No password provided");
6190
- }
6191
- const { collection, userField, users, relativePath } = await getUserDocumentContext(tinaSchema, resolver);
6192
- const { idFieldName, passwordFieldName } = userField;
6193
- const user = users.find((u) => u[idFieldName] === ctxUser.sub);
6194
- if (!user) {
6195
- throw new Error("Not authorized");
6196
- }
6197
- user[passwordFieldName] = {
6198
- value: password,
6199
- passwordChangeRequired: false
6200
- };
6201
- const newBody = {};
6202
- set(
6203
- newBody,
6204
- userField.path.slice(1),
6205
- // remove _rawData from users path
6206
- users.map((u) => {
6207
- if (user[idFieldName] === u[idFieldName]) {
6208
- return user;
6209
- }
6210
- return {
6211
- // don't overwrite other users' passwords
6212
- ...u,
6213
- [passwordFieldName]: {
6214
- ...u[passwordFieldName],
6215
- value: ""
6216
- }
6217
- };
6218
- })
6219
- );
6220
- await resolver.resolveUpdateDocument({
6221
- collectionName: collection.name,
6222
- relativePath,
6223
- newBody
6224
- });
6225
- return true;
6226
- }
6227
-
6228
6497
  // src/error.ts
6229
6498
  import { GraphQLError as GraphQLError3 } from "graphql";
6230
6499
  var NotFoundError = class extends GraphQLError3 {
@@ -6243,7 +6512,8 @@ var resolve = async ({
6243
6512
  silenceErrors,
6244
6513
  verbose,
6245
6514
  isAudit,
6246
- ctxUser
6515
+ ctxUser,
6516
+ isSignIn
6247
6517
  }) => {
6248
6518
  try {
6249
6519
  const verboseValue = verbose ?? true;
@@ -6262,7 +6532,8 @@ var resolve = async ({
6262
6532
  config,
6263
6533
  database,
6264
6534
  tinaSchema,
6265
- isAudit: isAudit || false
6535
+ isAudit: isAudit || false,
6536
+ ctxUser
6266
6537
  });
6267
6538
  const resolverPromises = [];
6268
6539
  const res = await graphql({
@@ -6306,7 +6577,7 @@ var resolve = async ({
6306
6577
  sub: authArgs.sub,
6307
6578
  password: authArgs.password,
6308
6579
  info,
6309
- ctxUser
6580
+ isSignIn
6310
6581
  });
6311
6582
  }
6312
6583
  if (info.fieldName === "authorize") {
@@ -6650,6 +6921,8 @@ var handleCollectionField = (info, args, resolver) => {
6650
6921
  import { ManyLevelGuest } from "many-level";
6651
6922
  import { pipeline } from "readable-stream";
6652
6923
  import { connect } from "net";
6924
+ var CONNECT_RETRIES = 50;
6925
+ var CONNECT_RETRY_DELAY_MS = 100;
6653
6926
  var TinaLevelClient = class extends ManyLevelGuest {
6654
6927
  port;
6655
6928
  _connected = false;
@@ -6659,10 +6932,30 @@ var TinaLevelClient = class extends ManyLevelGuest {
6659
6932
  }
6660
6933
  openConnection() {
6661
6934
  if (this._connected) return;
6662
- const socket = connect(this.port);
6663
- pipeline(socket, this.createRpcStream(), socket, () => {
6664
- this._connected = false;
6665
- });
6935
+ const rpcStream = this.createRpcStream();
6936
+ const tryConnect = (attempt) => {
6937
+ const socket = connect(this.port);
6938
+ const onConnectError = (err) => {
6939
+ socket.destroy();
6940
+ if (attempt < CONNECT_RETRIES) {
6941
+ setTimeout(() => tryConnect(attempt + 1), CONNECT_RETRY_DELAY_MS);
6942
+ } else {
6943
+ console.error(
6944
+ `TinaLevelClient: could not connect to the datalayer server on port ${this.port} after ${attempt + 1} attempts (${err.message}).`
6945
+ );
6946
+ rpcStream.destroy(err);
6947
+ this._connected = false;
6948
+ }
6949
+ };
6950
+ socket.once("error", onConnectError);
6951
+ socket.once("connect", () => {
6952
+ socket.removeListener("error", onConnectError);
6953
+ pipeline(socket, rpcStream, socket, () => {
6954
+ this._connected = false;
6955
+ });
6956
+ });
6957
+ };
6958
+ tryConnect(0);
6666
6959
  this._connected = true;
6667
6960
  }
6668
6961
  };
@@ -6672,7 +6965,11 @@ import path5 from "node:path";
6672
6965
  import { GraphQLError as GraphQLError5 } from "graphql";
6673
6966
  import micromatch2 from "micromatch";
6674
6967
  import sha2 from "js-sha1";
6675
- import { set as set2 } from "es-toolkit/compat";
6968
+ import { set } from "es-toolkit/compat";
6969
+ var withUnauthenticatedWrites = (options) => ({
6970
+ ...options,
6971
+ allowUnauthenticatedWrites: options?.allowUnauthenticatedWrites ?? true
6972
+ });
6676
6973
  var createLocalDatabase = (config) => {
6677
6974
  const level = new TinaLevelClient(config?.port);
6678
6975
  level.openConnection();
@@ -6680,7 +6977,8 @@ var createLocalDatabase = (config) => {
6680
6977
  return new Database({
6681
6978
  bridge: fsBridge,
6682
6979
  ...config || {},
6683
- level
6980
+ level,
6981
+ authCollection: withUnauthenticatedWrites(config?.authCollection)
6684
6982
  });
6685
6983
  };
6686
6984
  var createDatabase = (config) => {
@@ -6721,7 +7019,8 @@ var createDatabaseInternal = (config) => {
6721
7019
  return new Database({
6722
7020
  ...config,
6723
7021
  bridge: config.bridge,
6724
- level: config.level
7022
+ level: config.level,
7023
+ authCollection: withUnauthenticatedWrites(config.authCollection)
6725
7024
  });
6726
7025
  };
6727
7026
  var SYSTEM_FILES = ["_schema", "_graphql", "_lookup"];
@@ -6740,6 +7039,10 @@ var Database = class {
6740
7039
  this.onDelete = config.onDelete || defaultOnDelete;
6741
7040
  this.levelBatchSize = config.levelBatchSize ?? DEFAULT_LEVEL_BATCH_SIZE;
6742
7041
  this.contentNamespace = config.namespace;
7042
+ this.authCollection = Object.freeze({
7043
+ admins: Object.freeze([...config.authCollection?.admins ?? []]),
7044
+ allowUnauthenticatedWrites: config.authCollection?.allowUnauthenticatedWrites === true
7045
+ });
6743
7046
  }
6744
7047
  config;
6745
7048
  bridge;
@@ -6751,6 +7054,7 @@ var Database = class {
6751
7054
  onPut;
6752
7055
  onDelete;
6753
7056
  levelBatchSize;
7057
+ authCollection;
6754
7058
  tinaSchema;
6755
7059
  contentNamespace;
6756
7060
  collectionIndexDefinitions;
@@ -6871,7 +7175,7 @@ var Database = class {
6871
7175
  );
6872
7176
  const indexDefinitions = await this.getIndexDefinitions(this.contentLevel);
6873
7177
  const collectionIndexDefinitions = indexDefinitions?.[collection.name];
6874
- const collectionReferences = (await this.getCollectionReferences())?.[collection.name];
7178
+ const referenceSchema = await this.getSchema();
6875
7179
  const normalizedPath = normalizePath(filepath);
6876
7180
  if (!collection?.isDetached) {
6877
7181
  if (this.bridge) {
@@ -6903,7 +7207,7 @@ var Database = class {
6903
7207
  ...makeRefOpsForDocument(
6904
7208
  normalizedPath,
6905
7209
  collection?.name,
6906
- collectionReferences,
7210
+ referenceSchema,
6907
7211
  dataFields,
6908
7212
  "put",
6909
7213
  level
@@ -6934,7 +7238,7 @@ var Database = class {
6934
7238
  ...makeRefOpsForDocument(
6935
7239
  normalizedPath,
6936
7240
  collection?.name,
6937
- collectionReferences,
7241
+ referenceSchema,
6938
7242
  existingItem,
6939
7243
  "del",
6940
7244
  level
@@ -6986,7 +7290,7 @@ var Database = class {
6986
7290
  );
6987
7291
  collectionIndexDefinitions = indexDefinitions?.[collectionName];
6988
7292
  }
6989
- const collectionReferences = (await this.getCollectionReferences())?.[collectionName];
7293
+ const referenceSchema = await this.getSchema();
6990
7294
  const normalizedPath = normalizePath(filepath);
6991
7295
  const dataFields = await this.formatBodyOnPayload(filepath, data);
6992
7296
  const collection = await this.collectionForPath(filepath);
@@ -7040,7 +7344,7 @@ var Database = class {
7040
7344
  ...makeRefOpsForDocument(
7041
7345
  normalizedPath,
7042
7346
  collectionName,
7043
- collectionReferences,
7347
+ referenceSchema,
7044
7348
  dataFields,
7045
7349
  "put",
7046
7350
  level
@@ -7071,7 +7375,7 @@ var Database = class {
7071
7375
  ...makeRefOpsForDocument(
7072
7376
  normalizedPath,
7073
7377
  collectionName,
7074
- collectionReferences,
7378
+ referenceSchema,
7075
7379
  existingItem,
7076
7380
  "del",
7077
7381
  level
@@ -7347,7 +7651,7 @@ var Database = class {
7347
7651
  }
7348
7652
  if (fields) {
7349
7653
  for (const field of fields) {
7350
- if (field.indexed !== void 0 && field.indexed === false || field.type === "object") {
7654
+ if (field.indexed !== void 0 && field.indexed === false || field.type === "password" || field.type === "object") {
7351
7655
  continue;
7352
7656
  }
7353
7657
  indexDefinitions[field.name] = {
@@ -7497,32 +7801,41 @@ var Database = class {
7497
7801
  endKey = key || "";
7498
7802
  edges = [...edges, { cursor: key, path: filepath, value: itemRecord }];
7499
7803
  }
7500
- return {
7501
- edges: await sequential(
7502
- edges,
7503
- async ({
7504
- cursor,
7505
- path: path9,
7506
- value
7507
- }) => {
7508
- try {
7509
- const node = await hydrator(path9, value);
7510
- return {
7511
- node,
7512
- cursor: btoa(cursor)
7513
- };
7514
- } catch (error) {
7515
- console.log(error);
7516
- if (error instanceof Error && (!path9.includes(".tina/__generated__/_graphql.json") || !path9.includes("tina/__generated__/_graphql.json"))) {
7517
- throw new TinaQueryError({
7518
- originalError: error,
7519
- file: path9,
7520
- collection: collection.name
7521
- });
7522
- }
7523
- throw error;
7804
+ const hydratedEdges = await sequential(
7805
+ edges,
7806
+ async ({
7807
+ cursor,
7808
+ path: path9,
7809
+ value
7810
+ }) => {
7811
+ try {
7812
+ const node = await hydrator(path9, value);
7813
+ return {
7814
+ node,
7815
+ cursor: btoa(cursor)
7816
+ };
7817
+ } catch (error) {
7818
+ if (error instanceof NotFoundError) {
7819
+ console.warn(
7820
+ `Skipping ${path9} in collection ${collection.name}: its index entry has no record. Reindex to remove it.`
7821
+ );
7822
+ return null;
7823
+ }
7824
+ console.log(error);
7825
+ if (error instanceof Error && (!path9.includes(".tina/__generated__/_graphql.json") || !path9.includes("tina/__generated__/_graphql.json"))) {
7826
+ throw new TinaQueryError({
7827
+ originalError: error,
7828
+ file: path9,
7829
+ collection: collection.name
7830
+ });
7524
7831
  }
7832
+ throw error;
7525
7833
  }
7834
+ }
7835
+ );
7836
+ return {
7837
+ edges: hydratedEdges.filter(
7838
+ (edge) => edge !== null
7526
7839
  ),
7527
7840
  pageInfo: {
7528
7841
  hasPreviousPage,
@@ -7688,7 +8001,7 @@ var Database = class {
7688
8001
  throw new Error(`No collection found for path: ${filepath}`);
7689
8002
  }
7690
8003
  const indexDefinitions = await this.getIndexDefinitions(this.contentLevel);
7691
- const collectionReferences = (await this.getCollectionReferences())?.[collection.name];
8004
+ const referenceSchema = await this.getSchema();
7692
8005
  const collectionIndexDefinitions = indexDefinitions?.[collection.name];
7693
8006
  let level = this.contentLevel;
7694
8007
  if (collection?.isDetached) {
@@ -7710,7 +8023,7 @@ var Database = class {
7710
8023
  ...makeRefOpsForDocument(
7711
8024
  normalizedPath,
7712
8025
  collection.name,
7713
- collectionReferences,
8026
+ referenceSchema,
7714
8027
  item,
7715
8028
  "del",
7716
8029
  level
@@ -7812,7 +8125,7 @@ var hashPasswordVisitor = async (node, path9) => {
7812
8125
  const passwordValuePath = [...path9, "value"];
7813
8126
  const plaintextPassword = get(node, passwordValuePath);
7814
8127
  if (plaintextPassword) {
7815
- set2(
8128
+ set(
7816
8129
  node,
7817
8130
  passwordValuePath,
7818
8131
  await generatePasswordHash({ password: plaintextPassword })
@@ -7857,7 +8170,6 @@ var _indexContent = async ({
7857
8170
  }
7858
8171
  collectionPath = collection.path;
7859
8172
  }
7860
- const collectionReferences = (await database.getCollectionReferences())?.[collection?.name];
7861
8173
  const tinaSchema = await database.getSchema();
7862
8174
  let templateInfo = null;
7863
8175
  if (collection) {
@@ -7894,7 +8206,7 @@ var _indexContent = async ({
7894
8206
  ...makeRefOpsForDocument(
7895
8207
  normalizedPath,
7896
8208
  collection?.name,
7897
- collectionReferences,
8209
+ tinaSchema,
7898
8210
  item,
7899
8211
  "del",
7900
8212
  level
@@ -7930,7 +8242,7 @@ var _indexContent = async ({
7930
8242
  ...makeRefOpsForDocument(
7931
8243
  normalizedPath,
7932
8244
  collection?.name,
7933
- collectionReferences,
8245
+ tinaSchema,
7934
8246
  aliasedData,
7935
8247
  "put",
7936
8248
  level
@@ -8000,7 +8312,6 @@ var _deleteIndexContent = async (database, documentPaths, enqueueOps, collection
8000
8312
  throw new Error(`No indexDefinitions for collection ${collection.name}`);
8001
8313
  }
8002
8314
  }
8003
- const collectionReferences = (await database.getCollectionReferences())?.[collection?.name];
8004
8315
  const tinaSchema = await database.getSchema();
8005
8316
  let templateInfo = null;
8006
8317
  if (collection) {
@@ -8027,7 +8338,7 @@ var _deleteIndexContent = async (database, documentPaths, enqueueOps, collection
8027
8338
  ...makeRefOpsForDocument(
8028
8339
  itemKey,
8029
8340
  collection?.name,
8030
- collectionReferences,
8341
+ tinaSchema,
8031
8342
  aliasedData,
8032
8343
  "del",
8033
8344
  database.contentLevel