@tinacms/graphql 1.4.28 → 1.4.30

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.mjs CHANGED
@@ -458,6 +458,7 @@ var astBuilder = {
458
458
  CollectionDocumentUnion: "DocumentUnion",
459
459
  Folder: "Folder",
460
460
  String: "String",
461
+ Password: "Password",
461
462
  Reference: "Reference",
462
463
  Collection: "Collection",
463
464
  ID: "ID",
@@ -1268,6 +1269,125 @@ var scalarDefinitions = [
1268
1269
  ];
1269
1270
  var staticDefinitions = [...scalarDefinitions, interfaceDefinitions];
1270
1271
 
1272
+ // src/auth/utils.ts
1273
+ import crypto from "crypto";
1274
+ import scmp from "scmp";
1275
+ var DEFAULT_SALT_LENGTH = 32;
1276
+ var DEFAULT_KEY_LENGTH = 512;
1277
+ var DEFAULT_ITERATIONS = 25e3;
1278
+ var DEFAULT_DIGEST = "sha256";
1279
+ var generatePasswordHash = async ({
1280
+ password,
1281
+ opts: {
1282
+ saltLength = DEFAULT_SALT_LENGTH,
1283
+ keyLength = DEFAULT_KEY_LENGTH,
1284
+ iterations = DEFAULT_ITERATIONS,
1285
+ digest = DEFAULT_DIGEST
1286
+ } = {
1287
+ saltLength: DEFAULT_SALT_LENGTH,
1288
+ keyLength: DEFAULT_KEY_LENGTH,
1289
+ iterations: DEFAULT_ITERATIONS,
1290
+ digest: DEFAULT_DIGEST
1291
+ }
1292
+ }) => {
1293
+ if (!password) {
1294
+ throw new Error("Password is required");
1295
+ }
1296
+ if (password.length < 3) {
1297
+ throw new Error("Password must be at least 3 characters");
1298
+ }
1299
+ const salt = (await new Promise((resolve2, reject) => {
1300
+ crypto.randomBytes(saltLength, (err, saltBuffer) => {
1301
+ if (err) {
1302
+ reject(err);
1303
+ }
1304
+ resolve2(saltBuffer);
1305
+ });
1306
+ })).toString("hex");
1307
+ const hash = (await new Promise((resolve2, reject) => {
1308
+ crypto.pbkdf2(
1309
+ password,
1310
+ salt,
1311
+ iterations,
1312
+ keyLength,
1313
+ digest,
1314
+ (err, hashBuffer) => {
1315
+ if (err) {
1316
+ reject(err);
1317
+ }
1318
+ resolve2(hashBuffer);
1319
+ }
1320
+ );
1321
+ })).toString("hex");
1322
+ return `${salt}${hash}`;
1323
+ };
1324
+ var checkPasswordHash = async ({
1325
+ saltedHash,
1326
+ password,
1327
+ opts: {
1328
+ saltLength = DEFAULT_SALT_LENGTH,
1329
+ keyLength = DEFAULT_KEY_LENGTH,
1330
+ iterations = DEFAULT_ITERATIONS,
1331
+ digest = DEFAULT_DIGEST
1332
+ } = {
1333
+ saltLength: DEFAULT_SALT_LENGTH,
1334
+ keyLength: DEFAULT_KEY_LENGTH,
1335
+ iterations: DEFAULT_ITERATIONS,
1336
+ digest: DEFAULT_DIGEST
1337
+ }
1338
+ }) => {
1339
+ const salt = saltedHash.slice(0, saltLength * 2);
1340
+ const hash = saltedHash.slice(saltLength * 2);
1341
+ try {
1342
+ await new Promise((resolve2, reject) => {
1343
+ crypto.pbkdf2(
1344
+ password,
1345
+ salt,
1346
+ iterations,
1347
+ keyLength,
1348
+ digest,
1349
+ (err, hashBuffer) => {
1350
+ if (err) {
1351
+ reject(null);
1352
+ }
1353
+ if (scmp(hashBuffer, Buffer.from(hash, "hex"))) {
1354
+ resolve2();
1355
+ }
1356
+ reject(null);
1357
+ }
1358
+ );
1359
+ });
1360
+ } catch (e) {
1361
+ return false;
1362
+ }
1363
+ return true;
1364
+ };
1365
+ var mapUserFields = (collectable, prefix = []) => {
1366
+ const results = [];
1367
+ const passwordFields = collectable.fields?.filter((field) => field.type === "password") || [];
1368
+ if (passwordFields.length > 1) {
1369
+ throw new Error("Only one password field is allowed");
1370
+ }
1371
+ const idFields = collectable.fields?.filter((field) => field.uid) || [];
1372
+ if (idFields.length > 1) {
1373
+ throw new Error("Only one uid field is allowed");
1374
+ }
1375
+ if (passwordFields.length || idFields.length) {
1376
+ results.push({
1377
+ path: prefix,
1378
+ collectable,
1379
+ idFieldName: idFields[0]?.name,
1380
+ passwordFieldName: passwordFields[0]?.name
1381
+ });
1382
+ }
1383
+ collectable.fields?.forEach((field) => {
1384
+ if (field.type === "object" && field.fields) {
1385
+ results.push(...mapUserFields(field, [...prefix, field.name]));
1386
+ }
1387
+ });
1388
+ return results;
1389
+ };
1390
+
1271
1391
  // src/builder/index.ts
1272
1392
  var createBuilder = async ({
1273
1393
  tinaSchema
@@ -1368,7 +1488,7 @@ var Builder = class {
1368
1488
  type: astBuilder.TYPES.String
1369
1489
  })
1370
1490
  ];
1371
- await this.addToLookupMap({
1491
+ this.addToLookupMap({
1372
1492
  type: astBuilder.TYPES.Node,
1373
1493
  resolveType: "nodeDocument"
1374
1494
  });
@@ -1511,7 +1631,7 @@ var Builder = class {
1511
1631
  type: astBuilder.TYPES.String
1512
1632
  })
1513
1633
  ];
1514
- await this.addToLookupMap({
1634
+ this.addToLookupMap({
1515
1635
  type: type.name.value,
1516
1636
  resolveType: "collectionDocument",
1517
1637
  collection: collection.name,
@@ -1525,6 +1645,43 @@ var Builder = class {
1525
1645
  required: true
1526
1646
  });
1527
1647
  };
1648
+ this.authenticationCollectionDocument = async (collection) => {
1649
+ const name = "authenticate";
1650
+ const type = await this._buildAuthDocumentType(collection);
1651
+ const args = [
1652
+ astBuilder.InputValueDefinition({
1653
+ name: "sub",
1654
+ type: astBuilder.TYPES.String,
1655
+ required: true
1656
+ }),
1657
+ astBuilder.InputValueDefinition({
1658
+ name: "password",
1659
+ type: astBuilder.TYPES.String,
1660
+ required: true
1661
+ })
1662
+ ];
1663
+ return astBuilder.FieldDefinition({ type, name, args, required: false });
1664
+ };
1665
+ this.updatePasswordMutation = async (collection) => {
1666
+ return astBuilder.FieldDefinition({
1667
+ type: astBuilder.TYPES.Boolean,
1668
+ name: "updatePassword",
1669
+ required: true,
1670
+ args: [
1671
+ astBuilder.InputValueDefinition({
1672
+ name: "password",
1673
+ required: true,
1674
+ type: astBuilder.TYPES.String
1675
+ })
1676
+ ]
1677
+ });
1678
+ };
1679
+ this.authorizationCollectionDocument = async (collection) => {
1680
+ const name = "authorize";
1681
+ const type = await this._buildAuthDocumentType(collection);
1682
+ const args = [];
1683
+ return astBuilder.FieldDefinition({ type, name, args, required: false });
1684
+ };
1528
1685
  this.collectionFragment = async (collection) => {
1529
1686
  const name = NAMER.dataTypeName(collection.namespace);
1530
1687
  const fragmentName = NAMER.fragmentName(collection.namespace);
@@ -1567,6 +1724,29 @@ var Builder = class {
1567
1724
  case "boolean":
1568
1725
  case "rich-text":
1569
1726
  return astBuilder.FieldNodeDefinition(field);
1727
+ case "password":
1728
+ const passwordValue = await this._buildFieldNodeForFragments(
1729
+ {
1730
+ name: "value",
1731
+ namespace: [...field.namespace, "value"],
1732
+ type: "string",
1733
+ required: true
1734
+ },
1735
+ depth
1736
+ );
1737
+ const passwordChangeRequired = await this._buildFieldNodeForFragments(
1738
+ {
1739
+ name: "passwordChangeRequired",
1740
+ namespace: [...field.namespace, "passwordChangeRequired"],
1741
+ type: "boolean",
1742
+ required: false
1743
+ },
1744
+ depth
1745
+ );
1746
+ return astBuilder.FieldWithSelectionSetDefinition({
1747
+ name: field.name,
1748
+ selections: filterSelections([passwordValue, passwordChangeRequired])
1749
+ });
1570
1750
  case "object":
1571
1751
  if (field.fields?.length > 0) {
1572
1752
  const selections2 = [];
@@ -1780,6 +1960,34 @@ var Builder = class {
1780
1960
  ]
1781
1961
  });
1782
1962
  };
1963
+ this._buildAuthDocumentType = async (collection, suffix = "", extraFields = [], extraInterfaces = []) => {
1964
+ const usersFields = mapUserFields(collection, []);
1965
+ if (!usersFields.length) {
1966
+ throw new Error("Auth collection must have a user field");
1967
+ }
1968
+ if (usersFields.length > 1) {
1969
+ throw new Error("Auth collection cannot have more than one user field");
1970
+ }
1971
+ const usersField = usersFields[0].collectable;
1972
+ const documentTypeName = NAMER.documentTypeName(usersField.namespace);
1973
+ const templateInfo = this.tinaSchema.getTemplatesForCollectable(usersField);
1974
+ if (templateInfo.type === "union") {
1975
+ throw new Error("Auth collection user field cannot be a union");
1976
+ }
1977
+ const fields = templateInfo.template.fields;
1978
+ const templateFields = await sequential(fields, async (field) => {
1979
+ return this._buildDataField(field);
1980
+ });
1981
+ return astBuilder.ObjectTypeDefinition({
1982
+ name: documentTypeName + suffix,
1983
+ interfaces: [
1984
+ astBuilder.NamedType({ name: astBuilder.TYPES.Node }),
1985
+ astBuilder.NamedType({ name: astBuilder.TYPES.Document }),
1986
+ ...extraInterfaces
1987
+ ],
1988
+ fields: [...templateFields]
1989
+ });
1990
+ };
1783
1991
  this._filterCollectionDocumentType = async (collection) => {
1784
1992
  const t = this.tinaSchema.getTemplatesForCollectable(collection);
1785
1993
  if (t.type === "union") {
@@ -2038,9 +2246,7 @@ var Builder = class {
2038
2246
  const filter = await this._connectionFilterBuilder({
2039
2247
  fieldName: field.name,
2040
2248
  namespace: field.namespace,
2041
- collections: await this.tinaSchema.getCollectionsByName(
2042
- field.collections
2043
- )
2249
+ collections: this.tinaSchema.getCollectionsByName(field.collections)
2044
2250
  });
2045
2251
  return astBuilder.InputValueDefinition({
2046
2252
  name: field.name,
@@ -2073,6 +2279,8 @@ var Builder = class {
2073
2279
  list: field.list,
2074
2280
  type: astBuilder.TYPES.String
2075
2281
  });
2282
+ case "password":
2283
+ return this._buildPasswordMutation(field);
2076
2284
  case "object":
2077
2285
  return astBuilder.InputValueDefinition({
2078
2286
  name: field.name,
@@ -2107,6 +2315,27 @@ var Builder = class {
2107
2315
  )
2108
2316
  });
2109
2317
  };
2318
+ this._buildPasswordMutation = async (field) => {
2319
+ return astBuilder.InputValueDefinition({
2320
+ name: field.name,
2321
+ list: field.list,
2322
+ type: astBuilder.InputObjectTypeDefinition({
2323
+ name: NAMER.dataMutationTypeName(field.namespace),
2324
+ fields: [
2325
+ astBuilder.InputValueDefinition({
2326
+ name: "value",
2327
+ type: astBuilder.TYPES.String,
2328
+ required: false
2329
+ }),
2330
+ astBuilder.InputValueDefinition({
2331
+ name: "passwordChangeRequired",
2332
+ type: astBuilder.TYPES.Boolean,
2333
+ required: true
2334
+ })
2335
+ ]
2336
+ })
2337
+ });
2338
+ };
2110
2339
  this._buildUpdateDocumentMutationParams = async (field) => {
2111
2340
  const fields = await sequential(
2112
2341
  this.tinaSchema.getCollectionsByName(field.collections),
@@ -2271,6 +2500,29 @@ Visit https://tina.io/docs/errors/ui-not-supported/ for more information
2271
2500
  required: field.required,
2272
2501
  type: astBuilder.TYPES.Scalar(field.type)
2273
2502
  });
2503
+ case "password":
2504
+ return astBuilder.FieldDefinition({
2505
+ name: field.name,
2506
+ list: field.list,
2507
+ required: field.required,
2508
+ type: astBuilder.ObjectTypeDefinition({
2509
+ name: NAMER.dataTypeName(field.namespace),
2510
+ fields: [
2511
+ await this._buildDataField({
2512
+ name: "value",
2513
+ namespace: [...field.namespace, "value"],
2514
+ type: "string",
2515
+ required: true
2516
+ }),
2517
+ await this._buildDataField({
2518
+ name: "passwordChangeRequired",
2519
+ namespace: [...field.namespace, "passwordChangeRequired"],
2520
+ type: "boolean",
2521
+ required: false
2522
+ })
2523
+ ]
2524
+ })
2525
+ });
2274
2526
  case "object":
2275
2527
  return astBuilder.FieldDefinition({
2276
2528
  name: field.name,
@@ -2395,7 +2647,8 @@ var FIELD_TYPES = [
2395
2647
  "image",
2396
2648
  "reference",
2397
2649
  "object",
2398
- "rich-text"
2650
+ "rich-text",
2651
+ "password"
2399
2652
  ];
2400
2653
  var validateSchema = async (schema) => {
2401
2654
  const schema2 = addNamespaceToSchema(
@@ -2535,7 +2788,7 @@ var validateField = async (field) => {
2535
2788
  // package.json
2536
2789
  var package_default = {
2537
2790
  name: "@tinacms/graphql",
2538
- version: "1.4.28",
2791
+ version: "1.4.30",
2539
2792
  main: "dist/index.js",
2540
2793
  module: "dist/index.mjs",
2541
2794
  typings: "dist/index.d.ts",
@@ -2590,6 +2843,7 @@ var package_default = {
2590
2843
  micromatch: "4.0.5",
2591
2844
  "normalize-path": "^3.0.0",
2592
2845
  "readable-stream": "^4.3.0",
2846
+ scmp: "^2.1.0",
2593
2847
  yup: "^0.32.9"
2594
2848
  },
2595
2849
  publishConfig: {
@@ -2775,6 +3029,17 @@ var _buildSchema = async (builder, tinaSchema) => {
2775
3029
  );
2776
3030
  await sequential(collections, async (collection) => {
2777
3031
  queryTypeDefinitionFields.push(await builder.collectionDocument(collection));
3032
+ if (collection.isAuthCollection) {
3033
+ queryTypeDefinitionFields.push(
3034
+ await builder.authenticationCollectionDocument(collection)
3035
+ );
3036
+ queryTypeDefinitionFields.push(
3037
+ await builder.authorizationCollectionDocument(collection)
3038
+ );
3039
+ mutationTypeDefinitionFields.push(
3040
+ await builder.updatePasswordMutation(collection)
3041
+ );
3042
+ }
2778
3043
  mutationTypeDefinitionFields.push(
2779
3044
  await builder.updateCollectionDocumentMutation(collection)
2780
3045
  );
@@ -4081,6 +4346,12 @@ var resolveFieldData = async ({ namespace, ...field }, rawData, accumulator, tin
4081
4346
  accumulator[field.name] = value;
4082
4347
  }
4083
4348
  break;
4349
+ case "password":
4350
+ accumulator[field.name] = {
4351
+ value: void 0,
4352
+ passwordChangeRequired: value["passwordChangeRequired"] ?? false
4353
+ };
4354
+ break;
4084
4355
  case "image":
4085
4356
  accumulator[field.name] = resolveMediaRelativeToCloud(
4086
4357
  value,
@@ -4114,7 +4385,7 @@ var resolveFieldData = async ({ namespace, ...field }, rawData, accumulator, tin
4114
4385
  (yup3) => yup3.array().of(yup3.object().required())
4115
4386
  );
4116
4387
  accumulator[field.name] = await sequential(value, async (item) => {
4117
- const template = await tinaSchema.getTemplateForData({
4388
+ const template = tinaSchema.getTemplateForData({
4118
4389
  data: item,
4119
4390
  collection: {
4120
4391
  namespace,
@@ -4142,7 +4413,7 @@ var resolveFieldData = async ({ namespace, ...field }, rawData, accumulator, tin
4142
4413
  if (!value) {
4143
4414
  return;
4144
4415
  }
4145
- const template = await tinaSchema.getTemplateForData({
4416
+ const template = tinaSchema.getTemplateForData({
4146
4417
  data: value,
4147
4418
  collection: {
4148
4419
  namespace,
@@ -4305,43 +4576,71 @@ var Resolver = class {
4305
4576
  }
4306
4577
  await this.database.delete(fullPath);
4307
4578
  };
4308
- this.buildObjectMutations = (fieldValue, field) => {
4579
+ this.buildObjectMutations = async (fieldValue, field, existingData) => {
4309
4580
  if (field.fields) {
4310
4581
  const objectTemplate = field;
4311
4582
  if (Array.isArray(fieldValue)) {
4312
- return fieldValue.map(
4313
- (item) => this.buildFieldMutations(item, objectTemplate)
4583
+ const idField = objectTemplate.fields.find((field2) => field2.uid);
4584
+ if (idField) {
4585
+ const ids = fieldValue.map((d) => d[idField.name]);
4586
+ const duplicateIds = ids.filter(
4587
+ (id, index) => ids.indexOf(id) !== index
4588
+ );
4589
+ if (duplicateIds.length > 0) {
4590
+ throw new Error(
4591
+ `Duplicate ids found in array for field marked as identifier: ${idField.name}`
4592
+ );
4593
+ }
4594
+ }
4595
+ return Promise.all(
4596
+ fieldValue.map(
4597
+ async (item) => {
4598
+ return this.buildFieldMutations(
4599
+ item,
4600
+ objectTemplate,
4601
+ idField && existingData && existingData?.find(
4602
+ (d) => d[idField.name] === item[idField.name]
4603
+ )
4604
+ );
4605
+ }
4606
+ )
4314
4607
  );
4315
4608
  } else {
4316
4609
  return this.buildFieldMutations(
4317
4610
  fieldValue,
4318
- objectTemplate
4611
+ objectTemplate,
4612
+ existingData
4319
4613
  );
4320
4614
  }
4321
4615
  }
4322
4616
  if (field.templates) {
4323
4617
  if (Array.isArray(fieldValue)) {
4324
- return fieldValue.map((item) => {
4325
- if (typeof item === "string") {
4326
- throw new Error(
4327
- `Expected object for template value for field ${field.name}`
4618
+ return Promise.all(
4619
+ fieldValue.map(async (item) => {
4620
+ if (typeof item === "string") {
4621
+ throw new Error(
4622
+ `Expected object for template value for field ${field.name}`
4623
+ );
4624
+ }
4625
+ const templates = field.templates.map((templateOrTemplateName) => {
4626
+ return templateOrTemplateName;
4627
+ });
4628
+ const [templateName] = Object.entries(item)[0];
4629
+ const template = templates.find(
4630
+ (template2) => template2.name === templateName
4328
4631
  );
4329
- }
4330
- const templates = field.templates.map((templateOrTemplateName) => {
4331
- return templateOrTemplateName;
4332
- });
4333
- const [templateName] = Object.entries(item)[0];
4334
- const template = templates.find(
4335
- (template2) => template2.name === templateName
4336
- );
4337
- if (!template) {
4338
- throw new Error(`Expected to find template ${templateName}`);
4339
- }
4340
- return {
4341
- ...this.buildFieldMutations(item[template.name], template),
4342
- _template: template.name
4343
- };
4344
- });
4632
+ if (!template) {
4633
+ throw new Error(`Expected to find template ${templateName}`);
4634
+ }
4635
+ return {
4636
+ ...await this.buildFieldMutations(
4637
+ item[template.name],
4638
+ template
4639
+ ),
4640
+ _template: template.name
4641
+ };
4642
+ })
4643
+ );
4345
4644
  } else {
4346
4645
  if (typeof fieldValue === "string") {
4347
4646
  throw new Error(
@@ -4359,7 +4658,10 @@ var Resolver = class {
4359
4658
  throw new Error(`Expected to find template ${templateName}`);
4360
4659
  }
4361
4660
  return {
4362
- ...this.buildFieldMutations(fieldValue[template.name], template),
4661
+ ...await this.buildFieldMutations(
4662
+ fieldValue[template.name],
4663
+ template
4664
+ ),
4363
4665
  _template: template.name
4364
4666
  };
4365
4667
  }
@@ -4398,7 +4700,7 @@ var Resolver = class {
4398
4700
  }
4399
4701
  return this.getDocument(realPath);
4400
4702
  }
4401
- const params = this.buildObjectMutations(
4703
+ const params = await this.buildObjectMutations(
4402
4704
  args.params[collection.name],
4403
4705
  collection
4404
4706
  );
@@ -4420,9 +4722,10 @@ var Resolver = class {
4420
4722
  switch (templateInfo.type) {
4421
4723
  case "object":
4422
4724
  if (params2) {
4423
- const values = this.buildFieldMutations(
4725
+ const values = await this.buildFieldMutations(
4424
4726
  params2,
4425
- templateInfo.template
4727
+ templateInfo.template,
4728
+ doc?._rawData
4426
4729
  );
4427
4730
  await this.database.put(
4428
4731
  realPath,
@@ -4442,7 +4745,11 @@ var Resolver = class {
4442
4745
  }
4443
4746
  const values = {
4444
4747
  ...oldDoc,
4445
- ...this.buildFieldMutations(templateParams, template),
4748
+ ...await this.buildFieldMutations(
4749
+ templateParams,
4750
+ template,
4751
+ doc?._rawData
4752
+ ),
4446
4753
  _template: lastItem(template.namespace)
4447
4754
  };
4448
4755
  await this.database.put(realPath, values, collection.name);
@@ -4451,9 +4758,10 @@ var Resolver = class {
4451
4758
  }
4452
4759
  return this.getDocument(realPath);
4453
4760
  }
4454
- const params = this.buildObjectMutations(
4761
+ const params = await this.buildObjectMutations(
4455
4762
  isCollectionSpecific ? args.params : args.params[collection.name],
4456
- collection
4763
+ collection,
4764
+ doc?._rawData
4457
4765
  );
4458
4766
  await this.database.put(realPath, { ...oldDoc, ...params }, collection.name);
4459
4767
  return this.getDocument(realPath);
@@ -4682,13 +4990,23 @@ var Resolver = class {
4682
4990
  }
4683
4991
  };
4684
4992
  };
4685
- this.buildFieldMutations = (fieldParams, template) => {
4993
+ this.buildFieldMutations = async (fieldParams, template, existingData) => {
4686
4994
  const accum = {};
4687
- Object.entries(fieldParams).forEach(([fieldName, fieldValue]) => {
4995
+ for (const passwordField of template.fields.filter(
4996
+ (f) => f.type === "password"
4997
+ )) {
4998
+ if (!fieldParams[passwordField.name]["value"]) {
4999
+ fieldParams[passwordField.name] = {
5000
+ ...fieldParams[passwordField.name],
5001
+ value: ""
5002
+ };
5003
+ }
5004
+ }
5005
+ for (const [fieldName, fieldValue] of Object.entries(fieldParams)) {
4688
5006
  if (Array.isArray(fieldValue)) {
4689
5007
  if (fieldValue.length === 0) {
4690
5008
  accum[fieldName] = [];
4691
- return;
5009
+ break;
4692
5010
  }
4693
5011
  }
4694
5012
  const field = template.fields.find((field2) => field2.name === fieldName);
@@ -4712,7 +5030,31 @@ var Resolver = class {
4712
5030
  );
4713
5031
  break;
4714
5032
  case "object":
4715
- accum[fieldName] = this.buildObjectMutations(fieldValue, field);
5033
+ accum[fieldName] = await this.buildObjectMutations(
5034
+ fieldValue,
5035
+ field,
5036
+ existingData?.[fieldName]
5037
+ );
5038
+ break;
5039
+ case "password":
5040
+ if (typeof fieldValue !== "object") {
5041
+ throw new Error(
5042
+ `Expected to find object for password field ${fieldName}. Found ${typeof accum[fieldName]}`
5043
+ );
5044
+ }
5045
+ if (fieldValue["value"]) {
5046
+ accum[fieldName] = {
5047
+ ...fieldValue,
5048
+ value: await generatePasswordHash({
5049
+ password: fieldValue["value"]
5050
+ })
5051
+ };
5052
+ } else {
5053
+ accum[fieldName] = {
5054
+ ...fieldValue,
5055
+ value: existingData?.[fieldName]?.["value"]
5056
+ };
5057
+ }
4716
5058
  break;
4717
5059
  case "rich-text":
4718
5060
  accum[fieldName] = stringifyMDX(
@@ -4731,7 +5073,7 @@ var Resolver = class {
4731
5073
  default:
4732
5074
  throw new Error(`No mutation builder for field type ${field.type}`);
4733
5075
  }
4734
- });
5076
+ }
4735
5077
  return accum;
4736
5078
  };
4737
5079
  this.buildParams = (args) => {
@@ -4801,6 +5143,9 @@ var resolveDateInput = (value) => {
4801
5143
  return date;
4802
5144
  };
4803
5145
 
5146
+ // src/resolve.ts
5147
+ import _4 from "lodash";
5148
+
4804
5149
  // src/error.ts
4805
5150
  import { GraphQLError as GraphQLError3 } from "graphql";
4806
5151
  var NotFoundError = class extends GraphQLError3 {
@@ -4818,7 +5163,8 @@ var resolve = async ({
4818
5163
  database,
4819
5164
  silenceErrors,
4820
5165
  verbose,
4821
- isAudit
5166
+ isAudit,
5167
+ ctxUser
4822
5168
  }) => {
4823
5169
  try {
4824
5170
  const verboseValue = verbose ?? true;
@@ -4832,7 +5178,7 @@ var resolve = async ({
4832
5178
  schema: tinaConfig,
4833
5179
  flags: tinaConfig?.meta?.flags
4834
5180
  });
4835
- const resolver = await createResolver({
5181
+ const resolver = createResolver({
4836
5182
  config,
4837
5183
  database,
4838
5184
  tinaSchema,
@@ -4907,6 +5253,121 @@ var resolve = async ({
4907
5253
  );
4908
5254
  }
4909
5255
  }
5256
+ if (info.fieldName === "authenticate" || info.fieldName === "authorize") {
5257
+ const sub = args.sub || ctxUser?.sub;
5258
+ const collection = tinaSchema.getCollections().find((c) => c.isAuthCollection);
5259
+ if (!collection) {
5260
+ throw new Error(`Auth collection not found`);
5261
+ }
5262
+ const userFields = mapUserFields(collection, ["_rawData"]);
5263
+ if (!userFields.length) {
5264
+ throw new Error(
5265
+ `No user field found in collection ${collection.name}`
5266
+ );
5267
+ }
5268
+ if (userFields.length > 1) {
5269
+ throw new Error(
5270
+ `Multiple user fields found in collection ${collection.name}`
5271
+ );
5272
+ }
5273
+ const userField = userFields[0];
5274
+ const realPath = `${collection.path}/index.json`;
5275
+ const userDoc = await resolver.getDocument(realPath);
5276
+ const users = _4.get(userDoc, userField.path);
5277
+ if (!users) {
5278
+ throw new Error("No users found");
5279
+ }
5280
+ const { idFieldName, passwordFieldName } = userField;
5281
+ if (!idFieldName) {
5282
+ throw new Error("No uid field found on user field");
5283
+ }
5284
+ const user = users.find((u) => u[idFieldName] === sub);
5285
+ if (!user) {
5286
+ return null;
5287
+ }
5288
+ if (info.fieldName === "authenticate") {
5289
+ const saltedHash = _4.get(user, [passwordFieldName || "", "value"]);
5290
+ if (!saltedHash) {
5291
+ throw new Error("No password field found on user field");
5292
+ }
5293
+ const matches = await checkPasswordHash({
5294
+ saltedHash,
5295
+ password: args.password
5296
+ });
5297
+ if (matches) {
5298
+ return user;
5299
+ } else {
5300
+ return null;
5301
+ }
5302
+ } else {
5303
+ return user;
5304
+ }
5305
+ }
5306
+ if (info.fieldName === "updatePassword") {
5307
+ if (!ctxUser?.sub) {
5308
+ throw new Error("Not authorized");
5309
+ }
5310
+ if (!args.password) {
5311
+ throw new Error("No password provided");
5312
+ }
5313
+ const collection = tinaSchema.getCollections().find((c) => c.isAuthCollection);
5314
+ if (!collection) {
5315
+ throw new Error(`Auth collection not found`);
5316
+ }
5317
+ const userFields = mapUserFields(collection, ["_rawData"]);
5318
+ if (!userFields.length) {
5319
+ throw new Error(
5320
+ `No user field found in collection ${collection.name}`
5321
+ );
5322
+ }
5323
+ if (userFields.length > 1) {
5324
+ throw new Error(
5325
+ `Multiple user fields found in collection ${collection.name}`
5326
+ );
5327
+ }
5328
+ const userField = userFields[0];
5329
+ const realPath = `${collection.path}/index.json`;
5330
+ const userDoc = await resolver.getDocument(realPath);
5331
+ const users = _4.get(userDoc, userField.path);
5332
+ if (!users) {
5333
+ throw new Error("No users found");
5334
+ }
5335
+ const { idFieldName, passwordFieldName } = userField;
5336
+ const user = users.find((u) => u[idFieldName] === ctxUser.sub);
5337
+ if (!user) {
5338
+ throw new Error("Not authorized");
5339
+ }
5340
+ user[passwordFieldName] = {
5341
+ value: args.password,
5342
+ passwordChangeRequired: false
5343
+ };
5344
+ const params = {};
5345
+ _4.set(
5346
+ params,
5347
+ userField.path.slice(1),
5348
+ users.map((u) => {
5349
+ if (user[idFieldName] === u[idFieldName]) {
5350
+ return user;
5351
+ } else {
5352
+ return {
5353
+ ...u,
5354
+ [passwordFieldName]: {
5355
+ ...u[passwordFieldName],
5356
+ value: ""
5357
+ }
5358
+ };
5359
+ }
5360
+ })
5361
+ );
5362
+ await resolver.updateResolveDocument({
5363
+ collection,
5364
+ args: { params },
5365
+ realPath,
5366
+ isCollectionSpecific: true,
5367
+ isAddPendingDocument: false
5368
+ });
5369
+ return true;
5370
+ }
4910
5371
  if (!lookup) {
4911
5372
  return value;
4912
5373
  }
@@ -5047,12 +5508,78 @@ var resolve = async ({
5047
5508
  }
5048
5509
  };
5049
5510
 
5511
+ // src/level/tinaLevel.ts
5512
+ import { ManyLevelGuest } from "many-level";
5513
+ import { pipeline } from "readable-stream";
5514
+ import { connect } from "net";
5515
+ var TinaLevelClient = class extends ManyLevelGuest {
5516
+ constructor(port) {
5517
+ super();
5518
+ this._connected = false;
5519
+ this.port = port || 9e3;
5520
+ }
5521
+ openConnection() {
5522
+ if (this._connected)
5523
+ return;
5524
+ const socket = connect(this.port);
5525
+ pipeline(socket, this.createRpcStream(), socket, () => {
5526
+ this._connected = false;
5527
+ });
5528
+ this._connected = true;
5529
+ }
5530
+ };
5531
+
5050
5532
  // src/database/index.ts
5051
5533
  import path4 from "path";
5052
5534
  import { GraphQLError as GraphQLError5 } from "graphql";
5053
5535
  import micromatch2 from "micromatch";
5054
5536
  import sha2 from "js-sha1";
5537
+ import _5 from "lodash";
5538
+ var createLocalDatabase = (config) => {
5539
+ const level = new TinaLevelClient(config?.port);
5540
+ level.openConnection();
5541
+ const fsBridge = new FilesystemBridge(config?.rootPath || process.cwd());
5542
+ return new Database({
5543
+ bridge: fsBridge,
5544
+ ...config || {},
5545
+ level
5546
+ });
5547
+ };
5055
5548
  var createDatabase = (config) => {
5549
+ if (config.onPut && config.onDelete) {
5550
+ console.warn(
5551
+ "onPut and onDelete are deprecated. Please use gitProvider.onPut and gitProvider.onDelete instead."
5552
+ );
5553
+ }
5554
+ if (config.level) {
5555
+ console.warn("level is deprecated. Please use databaseAdapter instead.");
5556
+ }
5557
+ if (config.onPut && config.onDelete && config.level && !config.databaseAdapter && !config.gitProvider) {
5558
+ return new Database({
5559
+ ...config,
5560
+ level: config.level
5561
+ });
5562
+ }
5563
+ if (!config.gitProvider) {
5564
+ throw new Error(
5565
+ "createDatabase requires a gitProvider. Please provide a gitProvider."
5566
+ );
5567
+ }
5568
+ if (!config.databaseAdapter) {
5569
+ throw new Error(
5570
+ "createDatabase requires a databaseAdapter. Please provide a databaseAdapter."
5571
+ );
5572
+ }
5573
+ return new Database({
5574
+ ...config,
5575
+ bridge: config.bridge,
5576
+ level: config.databaseAdapter,
5577
+ onPut: config.gitProvider.onPut.bind(config.gitProvider),
5578
+ onDelete: config.gitProvider.onDelete.bind(config.gitProvider),
5579
+ namespace: config.namespace || "tinacms"
5580
+ });
5581
+ };
5582
+ var createDatabaseInternal = (config) => {
5056
5583
  return new Database({
5057
5584
  ...config,
5058
5585
  bridge: config.bridge,
@@ -5067,7 +5594,7 @@ var Database = class {
5067
5594
  constructor(config) {
5068
5595
  this.config = config;
5069
5596
  this.collectionForPath = async (filepath) => {
5070
- const tinaSchema = await this.getSchema(this.level);
5597
+ const tinaSchema = await this.getSchema(this.contentLevel);
5071
5598
  try {
5072
5599
  return tinaSchema.getCollectionByFullPath(filepath);
5073
5600
  } catch (e) {
@@ -5076,13 +5603,25 @@ var Database = class {
5076
5603
  this.getGeneratedFolder = () => path4.join(this.tinaDirectory, "__generated__");
5077
5604
  this.getMetadata = async (key) => {
5078
5605
  await this.initLevel();
5079
- const metadataLevel = this.rootLevel.sublevel("_metadata", SUBLEVEL_OPTIONS);
5606
+ let metadataLevel = this.rootLevel.sublevel("_metadata", SUBLEVEL_OPTIONS);
5607
+ if (this.contentNamespace) {
5608
+ metadataLevel = metadataLevel.sublevel(
5609
+ this.contentNamespace,
5610
+ SUBLEVEL_OPTIONS
5611
+ );
5612
+ }
5080
5613
  const metadata = await metadataLevel.get(`metadata_${key}`);
5081
5614
  return metadata?.value;
5082
5615
  };
5083
5616
  this.setMetadata = async (key, value) => {
5084
5617
  await this.initLevel();
5085
- const metadataLevel = this.rootLevel.sublevel("_metadata", SUBLEVEL_OPTIONS);
5618
+ let metadataLevel = this.rootLevel.sublevel("_metadata", SUBLEVEL_OPTIONS);
5619
+ if (this.contentNamespace) {
5620
+ metadataLevel = metadataLevel.sublevel(
5621
+ this.contentNamespace,
5622
+ SUBLEVEL_OPTIONS
5623
+ );
5624
+ }
5086
5625
  return metadataLevel.put(`metadata_${key}`, { value });
5087
5626
  };
5088
5627
  this.get = async (filepath) => {
@@ -5090,7 +5629,15 @@ var Database = class {
5090
5629
  if (SYSTEM_FILES.includes(filepath)) {
5091
5630
  throw new Error(`Unexpected get for config file ${filepath}`);
5092
5631
  } else {
5093
- const contentObject = await this.level.sublevel(
5632
+ let collection;
5633
+ let level = this.contentLevel;
5634
+ if (this.appLevel) {
5635
+ collection = await this.collectionForPath(filepath);
5636
+ if (collection?.isDetached) {
5637
+ level = this.appLevel.sublevel(collection.name, SUBLEVEL_OPTIONS);
5638
+ }
5639
+ }
5640
+ const contentObject = await level.sublevel(
5094
5641
  CONTENT_ROOT_PREFIX,
5095
5642
  SUBLEVEL_OPTIONS
5096
5643
  ).get(normalizePath(filepath));
@@ -5100,7 +5647,7 @@ var Database = class {
5100
5647
  return transformDocument(
5101
5648
  filepath,
5102
5649
  contentObject,
5103
- await this.getSchema(this.level)
5650
+ await this.getSchema(this.contentLevel)
5104
5651
  );
5105
5652
  }
5106
5653
  };
@@ -5116,23 +5663,29 @@ var Database = class {
5116
5663
  dataFields,
5117
5664
  collection
5118
5665
  );
5119
- const indexDefinitions = await this.getIndexDefinitions(this.level);
5666
+ const indexDefinitions = await this.getIndexDefinitions(this.contentLevel);
5120
5667
  const collectionIndexDefinitions = indexDefinitions?.[collection.name];
5121
5668
  const normalizedPath = normalizePath(filepath);
5122
- if (this.bridge) {
5123
- await this.bridge.put(normalizedPath, stringifiedFile);
5669
+ if (!collection?.isDetached) {
5670
+ if (this.bridge) {
5671
+ await this.bridge.put(normalizedPath, stringifiedFile);
5672
+ }
5673
+ try {
5674
+ await this.onPut(normalizedPath, stringifiedFile);
5675
+ } catch (e) {
5676
+ throw new GraphQLError5(
5677
+ `Error running onPut hook for ${filepath}: ${e}`,
5678
+ null,
5679
+ null,
5680
+ null,
5681
+ null,
5682
+ e
5683
+ );
5684
+ }
5124
5685
  }
5125
- try {
5126
- await this.onPut(normalizedPath, stringifiedFile);
5127
- } catch (e) {
5128
- throw new GraphQLError5(
5129
- `Error running onPut hook for ${filepath}: ${e}`,
5130
- null,
5131
- null,
5132
- null,
5133
- null,
5134
- e
5135
- );
5686
+ let level = this.contentLevel;
5687
+ if (collection?.isDetached) {
5688
+ level = this.appLevel.sublevel(collection.name, SUBLEVEL_OPTIONS);
5136
5689
  }
5137
5690
  const folderTreeBuilder = new FolderTreeBuilder();
5138
5691
  const folderKey = folderTreeBuilder.update(filepath, collection.path || "");
@@ -5143,7 +5696,7 @@ var Database = class {
5143
5696
  collectionIndexDefinitions,
5144
5697
  dataFields,
5145
5698
  "put",
5146
- this.level
5699
+ level
5147
5700
  ),
5148
5701
  ...makeIndexOpsForDocument(
5149
5702
  normalizedPath,
@@ -5151,10 +5704,10 @@ var Database = class {
5151
5704
  collectionIndexDefinitions,
5152
5705
  dataFields,
5153
5706
  "put",
5154
- this.level
5707
+ level
5155
5708
  )
5156
5709
  ];
5157
- const existingItem = await this.level.sublevel(
5710
+ const existingItem = await level.sublevel(
5158
5711
  CONTENT_ROOT_PREFIX,
5159
5712
  SUBLEVEL_OPTIONS
5160
5713
  ).get(normalizedPath);
@@ -5165,7 +5718,7 @@ var Database = class {
5165
5718
  collectionIndexDefinitions,
5166
5719
  existingItem,
5167
5720
  "del",
5168
- this.level
5721
+ level
5169
5722
  ),
5170
5723
  ...makeIndexOpsForDocument(
5171
5724
  normalizedPath,
@@ -5173,7 +5726,7 @@ var Database = class {
5173
5726
  collectionIndexDefinitions,
5174
5727
  existingItem,
5175
5728
  "del",
5176
- this.level
5729
+ level
5177
5730
  )
5178
5731
  ] : [];
5179
5732
  const ops = [
@@ -5183,13 +5736,13 @@ var Database = class {
5183
5736
  type: "put",
5184
5737
  key: normalizedPath,
5185
5738
  value: dataFields,
5186
- sublevel: this.level.sublevel(
5739
+ sublevel: level.sublevel(
5187
5740
  CONTENT_ROOT_PREFIX,
5188
5741
  SUBLEVEL_OPTIONS
5189
5742
  )
5190
5743
  }
5191
5744
  ];
5192
- await this.level.batch(ops);
5745
+ await level.batch(ops);
5193
5746
  };
5194
5747
  this.put = async (filepath, data, collectionName) => {
5195
5748
  await this.initLevel();
@@ -5199,7 +5752,9 @@ var Database = class {
5199
5752
  } else {
5200
5753
  let collectionIndexDefinitions;
5201
5754
  if (collectionName) {
5202
- const indexDefinitions = await this.getIndexDefinitions(this.level);
5755
+ const indexDefinitions = await this.getIndexDefinitions(
5756
+ this.contentLevel
5757
+ );
5203
5758
  collectionIndexDefinitions = indexDefinitions?.[collectionName];
5204
5759
  }
5205
5760
  const normalizedPath = normalizePath(filepath);
@@ -5224,26 +5779,29 @@ var Database = class {
5224
5779
  dataFields,
5225
5780
  collection
5226
5781
  );
5227
- if (this.bridge) {
5228
- await this.bridge.put(normalizedPath, stringifiedFile);
5229
- }
5230
- try {
5231
- await this.onPut(normalizedPath, stringifiedFile);
5232
- } catch (e) {
5233
- throw new GraphQLError5(
5234
- `Error running onPut hook for ${filepath}: ${e}`,
5235
- null,
5236
- null,
5237
- null,
5238
- null,
5239
- e
5240
- );
5782
+ if (!collection?.isDetached) {
5783
+ if (this.bridge) {
5784
+ await this.bridge.put(normalizedPath, stringifiedFile);
5785
+ }
5786
+ try {
5787
+ await this.onPut(normalizedPath, stringifiedFile);
5788
+ } catch (e) {
5789
+ throw new GraphQLError5(
5790
+ `Error running onPut hook for ${filepath}: ${e}`,
5791
+ null,
5792
+ null,
5793
+ null,
5794
+ null,
5795
+ e
5796
+ );
5797
+ }
5241
5798
  }
5242
5799
  const folderTreeBuilder = new FolderTreeBuilder();
5243
5800
  const folderKey = folderTreeBuilder.update(
5244
5801
  filepath,
5245
5802
  collection.path || ""
5246
5803
  );
5804
+ const level = collection?.isDetached ? this.appLevel.sublevel(collection?.name, SUBLEVEL_OPTIONS) : this.contentLevel;
5247
5805
  const putOps = [
5248
5806
  ...makeIndexOpsForDocument(
5249
5807
  normalizedPath,
@@ -5251,7 +5809,7 @@ var Database = class {
5251
5809
  collectionIndexDefinitions,
5252
5810
  dataFields,
5253
5811
  "put",
5254
- this.level
5812
+ level
5255
5813
  ),
5256
5814
  ...makeIndexOpsForDocument(
5257
5815
  normalizedPath,
@@ -5259,10 +5817,10 @@ var Database = class {
5259
5817
  collectionIndexDefinitions,
5260
5818
  dataFields,
5261
5819
  "put",
5262
- this.level
5820
+ level
5263
5821
  )
5264
5822
  ];
5265
- const existingItem = await this.level.sublevel(
5823
+ const existingItem = await level.sublevel(
5266
5824
  CONTENT_ROOT_PREFIX,
5267
5825
  SUBLEVEL_OPTIONS
5268
5826
  ).get(normalizedPath);
@@ -5273,7 +5831,7 @@ var Database = class {
5273
5831
  collectionIndexDefinitions,
5274
5832
  existingItem,
5275
5833
  "del",
5276
- this.level
5834
+ level
5277
5835
  ),
5278
5836
  ...makeIndexOpsForDocument(
5279
5837
  normalizedPath,
@@ -5281,7 +5839,7 @@ var Database = class {
5281
5839
  collectionIndexDefinitions,
5282
5840
  existingItem,
5283
5841
  "del",
5284
- this.level
5842
+ level
5285
5843
  )
5286
5844
  ] : [];
5287
5845
  const ops = [
@@ -5291,13 +5849,13 @@ var Database = class {
5291
5849
  type: "put",
5292
5850
  key: normalizedPath,
5293
5851
  value: dataFields,
5294
- sublevel: this.level.sublevel(
5852
+ sublevel: level.sublevel(
5295
5853
  CONTENT_ROOT_PREFIX,
5296
5854
  SUBLEVEL_OPTIONS
5297
5855
  )
5298
5856
  }
5299
5857
  ];
5300
- await this.level.batch(ops);
5858
+ await level.batch(ops);
5301
5859
  }
5302
5860
  return true;
5303
5861
  } catch (error) {
@@ -5347,7 +5905,7 @@ var Database = class {
5347
5905
  const writeTemplateKey = templateDetails.info.type === "union";
5348
5906
  const aliasedData = applyNameOverrides(templateDetails.template, payload);
5349
5907
  const extension = path4.extname(filepath);
5350
- const stringifiedFile = stringifyFile(
5908
+ return stringifyFile(
5351
5909
  aliasedData,
5352
5910
  extension,
5353
5911
  writeTemplateKey,
@@ -5356,7 +5914,6 @@ var Database = class {
5356
5914
  frontmatterDelimiters: collection?.frontmatterDelimiters
5357
5915
  }
5358
5916
  );
5359
- return stringifiedFile;
5360
5917
  };
5361
5918
  this.flush = async (filepath) => {
5362
5919
  const data = await this.get(filepath);
@@ -5365,12 +5922,7 @@ var Database = class {
5365
5922
  if (!collection) {
5366
5923
  throw new Error(`Unable to find collection for path ${filepath}`);
5367
5924
  }
5368
- const stringifiedFile = await this.stringifyFile(
5369
- filepath,
5370
- dataFields,
5371
- collection
5372
- );
5373
- return stringifiedFile;
5925
+ return this.stringifyFile(filepath, dataFields, collection);
5374
5926
  };
5375
5927
  this.getLookup = async (returnType) => {
5376
5928
  await this.initLevel();
@@ -5378,11 +5930,10 @@ var Database = class {
5378
5930
  path4.join(this.getGeneratedFolder(), `_lookup.json`)
5379
5931
  );
5380
5932
  if (!this._lookup) {
5381
- const _lookup = await this.level.sublevel(
5933
+ this._lookup = await this.contentLevel.sublevel(
5382
5934
  CONTENT_ROOT_PREFIX,
5383
5935
  SUBLEVEL_OPTIONS
5384
5936
  ).get(lookupPath);
5385
- this._lookup = _lookup;
5386
5937
  }
5387
5938
  return this._lookup[returnType];
5388
5939
  };
@@ -5391,7 +5942,7 @@ var Database = class {
5391
5942
  const graphqlPath = normalizePath(
5392
5943
  path4.join(this.getGeneratedFolder(), `_graphql.json`)
5393
5944
  );
5394
- return await this.level.sublevel(
5945
+ return await this.contentLevel.sublevel(
5395
5946
  CONTENT_ROOT_PREFIX,
5396
5947
  SUBLEVEL_OPTIONS
5397
5948
  ).get(graphqlPath);
@@ -5411,7 +5962,7 @@ var Database = class {
5411
5962
  const schemaPath = normalizePath(
5412
5963
  path4.join(this.getGeneratedFolder(), `_schema.json`)
5413
5964
  );
5414
- return await (level || this.level).sublevel(
5965
+ return await (level || this.contentLevel).sublevel(
5415
5966
  CONTENT_ROOT_PREFIX,
5416
5967
  SUBLEVEL_OPTIONS
5417
5968
  ).get(schemaPath);
@@ -5421,7 +5972,7 @@ var Database = class {
5421
5972
  return this.tinaSchema;
5422
5973
  }
5423
5974
  await this.initLevel();
5424
- const schema = existingSchema || await this.getTinaSchema(level || this.level);
5975
+ const schema = existingSchema || await this.getTinaSchema(level || this.contentLevel);
5425
5976
  if (!schema) {
5426
5977
  throw new Error(
5427
5978
  `Unable to get schema from level db: ${normalizePath(
@@ -5437,7 +5988,7 @@ var Database = class {
5437
5988
  await new Promise(async (resolve2, reject) => {
5438
5989
  await this.initLevel();
5439
5990
  try {
5440
- const schema = await this.getSchema(level || this.level);
5991
+ const schema = await this.getSchema(level || this.contentLevel);
5441
5992
  const collections = schema.getCollections();
5442
5993
  for (const collection of collections) {
5443
5994
  const indexDefinitions = {
@@ -5503,7 +6054,6 @@ var Database = class {
5503
6054
  last,
5504
6055
  before,
5505
6056
  sort = DEFAULT_COLLECTION_SORT_KEY,
5506
- collection,
5507
6057
  filterChain: rawFilterChain,
5508
6058
  folder
5509
6059
  } = queryOptions;
@@ -5519,22 +6069,25 @@ var Database = class {
5519
6069
  } else if (before) {
5520
6070
  query.lt = atob(before);
5521
6071
  }
5522
- const allIndexDefinitions = await this.getIndexDefinitions(this.level);
5523
- const indexDefinitions = allIndexDefinitions?.[queryOptions.collection];
6072
+ const tinaSchema = await this.getSchema(this.contentLevel);
6073
+ const collection = tinaSchema.getCollection(queryOptions.collection);
6074
+ const allIndexDefinitions = await this.getIndexDefinitions(
6075
+ this.contentLevel
6076
+ );
6077
+ const indexDefinitions = allIndexDefinitions?.[collection.name];
5524
6078
  if (!indexDefinitions) {
5525
- throw new Error(
5526
- `No indexDefinitions for collection ${queryOptions.collection}`
5527
- );
6079
+ throw new Error(`No indexDefinitions for collection ${collection.name}`);
5528
6080
  }
5529
6081
  const filterChain = coerceFilterChainOperands(rawFilterChain);
5530
6082
  const indexDefinition = sort && indexDefinitions?.[sort];
5531
6083
  const filterSuffixes = indexDefinition && makeFilterSuffixes(filterChain, indexDefinition);
5532
- const rootLevel = this.level.sublevel(
6084
+ const level = collection?.isDetached ? this.appLevel.sublevel(collection?.name, SUBLEVEL_OPTIONS) : this.contentLevel;
6085
+ const rootLevel = level.sublevel(
5533
6086
  CONTENT_ROOT_PREFIX,
5534
6087
  SUBLEVEL_OPTIONS
5535
6088
  );
5536
- const sublevel = indexDefinition ? this.level.sublevel(
5537
- `${collection}${folder ? `_${folder === FOLDER_ROOT ? folder : sha2.hex(folder)}` : ""}`,
6089
+ const sublevel = indexDefinition ? level.sublevel(
6090
+ `${collection.name}${folder ? `_${folder === FOLDER_ROOT ? folder : sha2.hex(folder)}` : ""}`,
5538
6091
  SUBLEVEL_OPTIONS
5539
6092
  ).sublevel(sort, SUBLEVEL_OPTIONS) : rootLevel;
5540
6093
  if (!query.gt && !query.gte) {
@@ -5606,7 +6159,7 @@ var Database = class {
5606
6159
  throw new TinaQueryError({
5607
6160
  originalError: error,
5608
6161
  file: edge.path,
5609
- collection,
6162
+ collection: collection.name,
5610
6163
  stack: error.stack
5611
6164
  });
5612
6165
  } else {
@@ -5654,8 +6207,8 @@ var Database = class {
5654
6207
  }
5655
6208
  let nextVersion;
5656
6209
  if (!this.config.version) {
5657
- await this.level.clear();
5658
- nextLevel = this.level;
6210
+ await this.contentLevel.clear();
6211
+ nextLevel = this.contentLevel;
5659
6212
  } else {
5660
6213
  const version = await this.getDatabaseVersion();
5661
6214
  nextVersion = version ? `${parseInt(version) + 1}` : "0";
@@ -5685,10 +6238,10 @@ var Database = class {
5685
6238
  },
5686
6239
  async () => {
5687
6240
  if (this.config.version) {
5688
- if (this.level) {
5689
- await this.level.clear();
6241
+ if (this.contentLevel) {
6242
+ await this.contentLevel.clear();
5690
6243
  }
5691
- this.level = nextLevel;
6244
+ this.contentLevel = nextLevel;
5692
6245
  }
5693
6246
  }
5694
6247
  );
@@ -5699,10 +6252,10 @@ var Database = class {
5699
6252
  const enqueueOps = async (ops) => {
5700
6253
  operations.push(...ops);
5701
6254
  while (operations.length >= 25) {
5702
- await this.level.batch(operations.splice(0, 25));
6255
+ await this.contentLevel.batch(operations.splice(0, 25));
5703
6256
  }
5704
6257
  };
5705
- const tinaSchema = await this.getSchema(this.level);
6258
+ const tinaSchema = await this.getSchema(this.contentLevel);
5706
6259
  await this.indexStatusCallbackWrapper(async () => {
5707
6260
  const { pathsByCollection, nonCollectionPaths, collections } = await partitionPathsByCollection(tinaSchema, documentPaths);
5708
6261
  for (const collection of Object.keys(pathsByCollection)) {
@@ -5718,7 +6271,7 @@ var Database = class {
5718
6271
  }
5719
6272
  });
5720
6273
  while (operations.length) {
5721
- await this.level.batch(operations.splice(0, 25));
6274
+ await this.contentLevel.batch(operations.splice(0, 25));
5722
6275
  }
5723
6276
  };
5724
6277
  this.indexContentByPaths = async (documentPaths) => {
@@ -5727,31 +6280,29 @@ var Database = class {
5727
6280
  const enqueueOps = async (ops) => {
5728
6281
  operations.push(...ops);
5729
6282
  while (operations.length >= 25) {
5730
- await this.level.batch(operations.splice(0, 25));
6283
+ await this.contentLevel.batch(operations.splice(0, 25));
5731
6284
  }
5732
6285
  };
5733
- const tinaSchema = await this.getSchema(this.level);
6286
+ const tinaSchema = await this.getSchema(this.contentLevel);
5734
6287
  await this.indexStatusCallbackWrapper(async () => {
5735
6288
  await scanContentByPaths(
5736
6289
  tinaSchema,
5737
6290
  documentPaths,
5738
6291
  async (collection, documentPaths2) => {
5739
- if (collection) {
6292
+ if (collection && !collection.isDetached) {
5740
6293
  await _indexContent(
5741
6294
  this,
5742
- this.level,
6295
+ this.contentLevel,
5743
6296
  documentPaths2,
5744
6297
  enqueueOps,
5745
6298
  collection
5746
6299
  );
5747
- } else {
5748
- await _indexContent(this, this.level, documentPaths2, enqueueOps);
5749
6300
  }
5750
6301
  }
5751
6302
  );
5752
6303
  });
5753
6304
  while (operations.length) {
5754
- await this.level.batch(operations.splice(0, 25));
6305
+ await this.contentLevel.batch(operations.splice(0, 25));
5755
6306
  }
5756
6307
  };
5757
6308
  this.delete = async (filepath) => {
@@ -5760,14 +6311,14 @@ var Database = class {
5760
6311
  if (!collection) {
5761
6312
  throw new Error(`No collection found for path: ${filepath}`);
5762
6313
  }
5763
- const indexDefinitions = await this.getIndexDefinitions(this.level);
6314
+ const indexDefinitions = await this.getIndexDefinitions(this.contentLevel);
5764
6315
  const collectionIndexDefinitions = indexDefinitions?.[collection.name];
5765
- this.level.sublevel(
5766
- CONTENT_ROOT_PREFIX,
5767
- SUBLEVEL_OPTIONS
5768
- );
6316
+ let level = this.contentLevel;
6317
+ if (collection?.isDetached) {
6318
+ level = this.appLevel.sublevel(collection?.name, SUBLEVEL_OPTIONS);
6319
+ }
5769
6320
  const itemKey = normalizePath(filepath);
5770
- const rootSublevel = this.level.sublevel(
6321
+ const rootSublevel = level.sublevel(
5771
6322
  CONTENT_ROOT_PREFIX,
5772
6323
  SUBLEVEL_OPTIONS
5773
6324
  );
@@ -5778,14 +6329,14 @@ var Database = class {
5778
6329
  filepath,
5779
6330
  collection.path || ""
5780
6331
  );
5781
- await this.level.batch([
6332
+ await this.contentLevel.batch([
5782
6333
  ...makeIndexOpsForDocument(
5783
6334
  filepath,
5784
6335
  collection.name,
5785
6336
  collectionIndexDefinitions,
5786
6337
  item,
5787
6338
  "del",
5788
- this.level
6339
+ level
5789
6340
  ),
5790
6341
  ...makeIndexOpsForDocument(
5791
6342
  filepath,
@@ -5793,7 +6344,7 @@ var Database = class {
5793
6344
  collectionIndexDefinitions,
5794
6345
  item,
5795
6346
  "del",
5796
- this.level
6347
+ level
5797
6348
  ),
5798
6349
  {
5799
6350
  type: "del",
@@ -5802,20 +6353,22 @@ var Database = class {
5802
6353
  }
5803
6354
  ]);
5804
6355
  }
5805
- if (this.bridge) {
5806
- await this.bridge.delete(normalizePath(filepath));
5807
- }
5808
- try {
5809
- await this.onDelete(normalizePath(filepath));
5810
- } catch (e) {
5811
- throw new GraphQLError5(
5812
- `Error running onDelete hook for ${filepath}: ${e}`,
5813
- null,
5814
- null,
5815
- null,
5816
- null,
5817
- e
5818
- );
6356
+ if (!collection?.isDetached) {
6357
+ if (this.bridge) {
6358
+ await this.bridge.delete(normalizePath(filepath));
6359
+ }
6360
+ try {
6361
+ await this.onDelete(normalizePath(filepath));
6362
+ } catch (e) {
6363
+ throw new GraphQLError5(
6364
+ `Error running onDelete hook for ${filepath}: ${e}`,
6365
+ null,
6366
+ null,
6367
+ null,
6368
+ null,
6369
+ e
6370
+ );
6371
+ }
5819
6372
  }
5820
6373
  };
5821
6374
  this._indexAllContent = async (level, schema) => {
@@ -5832,7 +6385,29 @@ var Database = class {
5832
6385
  tinaSchema,
5833
6386
  this.bridge,
5834
6387
  async (collection, contentPaths) => {
5835
- await _indexContent(this, level, contentPaths, enqueueOps, collection);
6388
+ const userFields = mapUserFields(collection, []);
6389
+ if (collection.isDetached) {
6390
+ const level2 = this.appLevel.sublevel(
6391
+ collection.name,
6392
+ SUBLEVEL_OPTIONS
6393
+ );
6394
+ const doc = await level2.keys({ limit: 1 }).next();
6395
+ if (!doc) {
6396
+ await _indexContent(
6397
+ this,
6398
+ level2,
6399
+ contentPaths,
6400
+ enqueueOps,
6401
+ collection,
6402
+ userFields.map((field) => [
6403
+ ...field.path,
6404
+ field.passwordFieldName
6405
+ ])
6406
+ );
6407
+ }
6408
+ } else {
6409
+ await _indexContent(this, level, contentPaths, enqueueOps, collection);
6410
+ }
5836
6411
  }
5837
6412
  );
5838
6413
  while (operations.length) {
@@ -5846,22 +6421,36 @@ var Database = class {
5846
6421
  this.indexStatusCallback = config.indexStatusCallback || defaultStatusCallback;
5847
6422
  this.onPut = config.onPut || defaultOnPut;
5848
6423
  this.onDelete = config.onDelete || defaultOnDelete;
6424
+ this.contentNamespace = config.namespace;
5849
6425
  }
5850
6426
  async updateDatabaseVersion(version) {
5851
- const metadataLevel = this.rootLevel.sublevel("_metadata", SUBLEVEL_OPTIONS);
6427
+ let metadataLevel = this.rootLevel.sublevel("_metadata", SUBLEVEL_OPTIONS);
6428
+ if (this.contentNamespace) {
6429
+ metadataLevel = metadataLevel.sublevel(
6430
+ this.contentNamespace,
6431
+ SUBLEVEL_OPTIONS
6432
+ );
6433
+ }
5852
6434
  await metadataLevel.put("metadata", { version });
5853
6435
  }
5854
6436
  async getDatabaseVersion() {
5855
- const metadataLevel = this.rootLevel.sublevel("_metadata", SUBLEVEL_OPTIONS);
6437
+ let metadataLevel = this.rootLevel.sublevel("_metadata", SUBLEVEL_OPTIONS);
6438
+ if (this.contentNamespace) {
6439
+ metadataLevel = metadataLevel.sublevel(
6440
+ this.contentNamespace,
6441
+ SUBLEVEL_OPTIONS
6442
+ );
6443
+ }
5856
6444
  const metadata = await metadataLevel.get("metadata");
5857
6445
  return metadata?.version;
5858
6446
  }
5859
6447
  async initLevel() {
5860
- if (this.level) {
6448
+ if (this.contentLevel) {
5861
6449
  return;
5862
6450
  }
6451
+ this.appLevel = this.rootLevel.sublevel("_appData", SUBLEVEL_OPTIONS);
5863
6452
  if (!this.config.version) {
5864
- this.level = this.rootLevel;
6453
+ this.contentLevel = this.contentNamespace ? this.rootLevel.sublevel("_content", SUBLEVEL_OPTIONS).sublevel(this.contentNamespace, SUBLEVEL_OPTIONS) : this.rootLevel.sublevel("_content", SUBLEVEL_OPTIONS);
5865
6454
  } else {
5866
6455
  let version = await this.getDatabaseVersion();
5867
6456
  if (!version) {
@@ -5871,9 +6460,9 @@ var Database = class {
5871
6460
  } catch (e) {
5872
6461
  }
5873
6462
  }
5874
- this.level = this.rootLevel.sublevel(version, SUBLEVEL_OPTIONS);
6463
+ this.contentLevel = this.contentNamespace ? this.rootLevel.sublevel("_content").sublevel(this.contentNamespace, SUBLEVEL_OPTIONS).sublevel(version, SUBLEVEL_OPTIONS) : this.rootLevel.sublevel(version, SUBLEVEL_OPTIONS);
5875
6464
  }
5876
- if (!this.level) {
6465
+ if (!this.contentLevel) {
5877
6466
  throw new GraphQLError5("Error initializing LevelDB instance");
5878
6467
  }
5879
6468
  }
@@ -5922,7 +6511,36 @@ var Database = class {
5922
6511
  }
5923
6512
  }
5924
6513
  };
5925
- var _indexContent = async (database, level, documentPaths, enqueueOps, collection) => {
6514
+ var hashPasswordVisitor = async (node, path7) => {
6515
+ const passwordValuePath = [...path7, "value"];
6516
+ const plaintextPassword = _5.get(node, passwordValuePath);
6517
+ if (plaintextPassword) {
6518
+ _5.set(
6519
+ node,
6520
+ passwordValuePath,
6521
+ await generatePasswordHash({ password: plaintextPassword })
6522
+ );
6523
+ }
6524
+ };
6525
+ var visitNodes = async (node, path7, callback) => {
6526
+ const [currentLevel, ...remainingLevels] = path7;
6527
+ if (!remainingLevels?.length) {
6528
+ return callback(node, path7);
6529
+ }
6530
+ if (Array.isArray(node[currentLevel])) {
6531
+ for (const item of node[currentLevel]) {
6532
+ await visitNodes(item, remainingLevels, callback);
6533
+ }
6534
+ } else {
6535
+ await visitNodes(node[currentLevel], remainingLevels, callback);
6536
+ }
6537
+ };
6538
+ var hashPasswordValues = async (data, passwordFields) => Promise.all(
6539
+ passwordFields.map(
6540
+ async (passwordField) => visitNodes(data, passwordField, hashPasswordVisitor)
6541
+ )
6542
+ );
6543
+ var _indexContent = async (database, level, documentPaths, enqueueOps, collection, passwordFields) => {
5926
6544
  let collectionIndexDefinitions;
5927
6545
  let collectionPath;
5928
6546
  if (collection) {
@@ -5950,6 +6568,9 @@ var _indexContent = async (database, level, documentPaths, enqueueOps, collectio
5950
6568
  if (!aliasedData) {
5951
6569
  return;
5952
6570
  }
6571
+ if (passwordFields?.length) {
6572
+ await hashPasswordValues(aliasedData, passwordFields);
6573
+ }
5953
6574
  const normalizedPath = normalizePath(filepath);
5954
6575
  const folderKey = folderTreeBuilder.update(
5955
6576
  normalizedPath,
@@ -6009,7 +6630,9 @@ var _deleteIndexContent = async (database, documentPaths, enqueueOps, collection
6009
6630
  }
6010
6631
  let collectionIndexDefinitions;
6011
6632
  if (collection) {
6012
- const indexDefinitions = await database.getIndexDefinitions(database.level);
6633
+ const indexDefinitions = await database.getIndexDefinitions(
6634
+ database.contentLevel
6635
+ );
6013
6636
  collectionIndexDefinitions = indexDefinitions?.[collection.name];
6014
6637
  if (!collectionIndexDefinitions) {
6015
6638
  throw new Error(`No indexDefinitions for collection ${collection.name}`);
@@ -6018,9 +6641,9 @@ var _deleteIndexContent = async (database, documentPaths, enqueueOps, collection
6018
6641
  const tinaSchema = await database.getSchema();
6019
6642
  let templateInfo = null;
6020
6643
  if (collection) {
6021
- templateInfo = await tinaSchema.getTemplatesForCollectable(collection);
6644
+ templateInfo = tinaSchema.getTemplatesForCollectable(collection);
6022
6645
  }
6023
- const rootLevel = database.level.sublevel(
6646
+ const rootLevel = database.contentLevel.sublevel(
6024
6647
  CONTENT_ROOT_PREFIX,
6025
6648
  SUBLEVEL_OPTIONS
6026
6649
  );
@@ -6044,7 +6667,7 @@ var _deleteIndexContent = async (database, documentPaths, enqueueOps, collection
6044
6667
  collectionIndexDefinitions,
6045
6668
  aliasedData,
6046
6669
  "del",
6047
- database.level
6670
+ database.contentLevel
6048
6671
  ),
6049
6672
  ...makeIndexOpsForDocument(
6050
6673
  itemKey,
@@ -6052,7 +6675,7 @@ var _deleteIndexContent = async (database, documentPaths, enqueueOps, collection
6052
6675
  collectionIndexDefinitions,
6053
6676
  aliasedData,
6054
6677
  "del",
6055
- database.level
6678
+ database.contentLevel
6056
6679
  ),
6057
6680
  { type: "del", key: itemKey, sublevel: rootLevel }
6058
6681
  ]);
@@ -6065,33 +6688,12 @@ var _deleteIndexContent = async (database, documentPaths, enqueueOps, collection
6065
6688
  collection,
6066
6689
  collectionIndexDefinitions,
6067
6690
  "del",
6068
- database.level
6691
+ database.contentLevel
6069
6692
  )
6070
6693
  );
6071
6694
  }
6072
6695
  };
6073
6696
 
6074
- // src/level/tinaLevel.ts
6075
- import { ManyLevelGuest } from "many-level";
6076
- import { pipeline } from "readable-stream";
6077
- import { connect } from "net";
6078
- var TinaLevelClient = class extends ManyLevelGuest {
6079
- constructor(port) {
6080
- super();
6081
- this._connected = false;
6082
- this.port = port || 9e3;
6083
- }
6084
- openConnection() {
6085
- if (this._connected)
6086
- return;
6087
- const socket = connect(this.port);
6088
- pipeline(socket, this.createRpcStream(), socket, () => {
6089
- this._connected = false;
6090
- });
6091
- this._connected = true;
6092
- }
6093
- };
6094
-
6095
6697
  // src/git/index.ts
6096
6698
  import git from "isomorphic-git";
6097
6699
  import fs from "fs-extra";
@@ -6627,6 +7229,7 @@ var buildSchema = async (config, flags) => {
6627
7229
  };
6628
7230
  export {
6629
7231
  AuditFileSystemBridge,
7232
+ Database,
6630
7233
  FilesystemBridge,
6631
7234
  IsomorphicBridge,
6632
7235
  TinaFetchError,
@@ -6637,12 +7240,17 @@ export {
6637
7240
  assertShape,
6638
7241
  buildDotTinaFiles,
6639
7242
  buildSchema,
7243
+ checkPasswordHash,
6640
7244
  createDatabase,
7245
+ createDatabaseInternal,
7246
+ createLocalDatabase,
6641
7247
  createSchema,
7248
+ generatePasswordHash,
6642
7249
  getChangedFiles,
6643
7250
  getSha,
6644
7251
  handleFetchErrorError,
6645
7252
  loadAndParseWithAliases,
7253
+ mapUserFields,
6646
7254
  parseFile,
6647
7255
  resolve,
6648
7256
  scanAllContent,