@vltpkg/vsr 0.0.0-27 → 0.0.0-29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/DEPLOY.md +163 -0
  2. package/LICENSE +114 -10
  3. package/config.ts +221 -0
  4. package/dist/README.md +1 -1
  5. package/dist/bin/vsr.js +9 -6
  6. package/dist/index.js +4 -6
  7. package/dist/index.js.map +2 -2
  8. package/drizzle.config.js +40 -0
  9. package/info/COMPARISONS.md +37 -0
  10. package/info/CONFIGURATION.md +143 -0
  11. package/info/CONTRIBUTING.md +32 -0
  12. package/info/DATABASE_SETUP.md +108 -0
  13. package/info/GRANULAR_ACCESS_TOKENS.md +160 -0
  14. package/info/PROJECT_STRUCTURE.md +291 -0
  15. package/info/ROADMAP.md +27 -0
  16. package/info/SUPPORT.md +39 -0
  17. package/info/TESTING.md +301 -0
  18. package/info/USER_SUPPORT.md +31 -0
  19. package/package.json +50 -6
  20. package/scripts/build-assets.js +31 -0
  21. package/scripts/build-bin.js +63 -0
  22. package/src/assets/public/images/bg.png +0 -0
  23. package/src/assets/public/images/clients/logo-bun.png +0 -0
  24. package/src/assets/public/images/clients/logo-deno.png +0 -0
  25. package/src/assets/public/images/clients/logo-npm.png +0 -0
  26. package/src/assets/public/images/clients/logo-pnpm.png +0 -0
  27. package/src/assets/public/images/clients/logo-vlt.png +0 -0
  28. package/src/assets/public/images/clients/logo-yarn.png +0 -0
  29. package/src/assets/public/images/favicon/apple-touch-icon.png +0 -0
  30. package/src/assets/public/images/favicon/favicon-96x96.png +0 -0
  31. package/src/assets/public/images/favicon/favicon.ico +0 -0
  32. package/src/assets/public/images/favicon/favicon.svg +3 -0
  33. package/src/assets/public/images/favicon/site.webmanifest +21 -0
  34. package/src/assets/public/images/favicon/web-app-manifest-192x192.png +0 -0
  35. package/src/assets/public/images/favicon/web-app-manifest-512x512.png +0 -0
  36. package/src/assets/public/styles/styles.css +231 -0
  37. package/src/bin/demo/package.json +6 -0
  38. package/src/bin/demo/vlt.json +1 -0
  39. package/src/bin/vsr.ts +496 -0
  40. package/src/db/client.ts +590 -0
  41. package/src/db/migrations/0000_faulty_ricochet.sql +14 -0
  42. package/src/db/migrations/0000_initial.sql +29 -0
  43. package/src/db/migrations/0001_uuid_validation.sql +35 -0
  44. package/src/db/migrations/0001_wealthy_magdalene.sql +7 -0
  45. package/src/db/migrations/drop.sql +3 -0
  46. package/src/db/migrations/meta/0000_snapshot.json +104 -0
  47. package/src/db/migrations/meta/0001_snapshot.json +155 -0
  48. package/src/db/migrations/meta/_journal.json +20 -0
  49. package/src/db/schema.ts +43 -0
  50. package/src/index.ts +434 -0
  51. package/src/middleware/config.ts +79 -0
  52. package/src/middleware/telemetry.ts +43 -0
  53. package/src/queue/index.ts +97 -0
  54. package/src/routes/access.ts +852 -0
  55. package/src/routes/docs.ts +63 -0
  56. package/src/routes/misc.ts +469 -0
  57. package/src/routes/packages.ts +2823 -0
  58. package/src/routes/ping.ts +39 -0
  59. package/src/routes/search.ts +131 -0
  60. package/src/routes/static.ts +74 -0
  61. package/src/routes/tokens.ts +259 -0
  62. package/src/routes/users.ts +68 -0
  63. package/src/utils/auth.ts +202 -0
  64. package/src/utils/cache.ts +587 -0
  65. package/src/utils/config.ts +50 -0
  66. package/src/utils/database.ts +69 -0
  67. package/src/utils/docs.ts +146 -0
  68. package/src/utils/packages.ts +453 -0
  69. package/src/utils/response.ts +125 -0
  70. package/src/utils/routes.ts +64 -0
  71. package/src/utils/spa.ts +52 -0
  72. package/src/utils/tracing.ts +52 -0
  73. package/src/utils/upstream.ts +172 -0
  74. package/tsconfig.json +16 -0
  75. package/tsconfig.worker.json +3 -0
  76. package/typedoc.mjs +2 -0
  77. package/types.ts +598 -0
  78. package/vitest.config.ts +25 -0
  79. package/vlt.json.example +56 -0
  80. package/wrangler.json +65 -0
@@ -0,0 +1,40 @@
1
+ import { defineConfig } from 'drizzle-kit'
2
+ import { join } from 'path'
3
+ import { existsSync, readdirSync } from 'fs'
4
+
5
+ // Find the actual SQLite database file in the miniflare-D1DatabaseObject directory
6
+ const miniflareDir = join(
7
+ import.meta.dirname,
8
+ './local-store',
9
+ 'v3',
10
+ 'd1',
11
+ 'miniflare-D1DatabaseObject',
12
+ )
13
+
14
+ if (!existsSync(miniflareDir)) {
15
+ throw new Error(
16
+ `Miniflare directory not found at ${miniflareDir}. Make sure to run \`pnpm run db:setup\` first.`,
17
+ )
18
+ }
19
+ // Look for the most recently modified SQLite file
20
+ const file = readdirSync(miniflareDir, { withFileTypes: true })
21
+ .filter(file => file.isFile() && file.name.endsWith('.sqlite'))
22
+ .sort((a, b) => b.mtime.getTime() - a.mtime.getTime())
23
+ .map(file => join(file.parentPath, file.name))
24
+ .find(file => existsSync(file))
25
+
26
+ if (!file) {
27
+ throw new Error(
28
+ `No SQLite file found in ${miniflareDir}. Make sure to run \`pnpm run db:setup\` first.`,
29
+ )
30
+ }
31
+
32
+ // For Drizzle Kit
33
+ export default defineConfig({
34
+ schema: './src/db/schema.ts',
35
+ out: './src/db/migrations',
36
+ dialect: 'sqlite',
37
+ dbCredentials: {
38
+ url: file,
39
+ },
40
+ })
@@ -0,0 +1,37 @@
1
+ # Registry Comparisons
2
+
3
+ | Feature | **vsr** | **npm** | **GitHub** | **Verdaccio** | **JSR** | **jFrog** | **Sonatype** | **Cloudsmith** | **Buildkite** | **Bit** |
4
+ | ---------------------------- | :-----------: | :-------------: | :-------------: | :-----------: | :-----: | :-------------: | :-------------: | :-------------: | :-------------: | :-------------: |
5
+ | License | `FSL-1.1-MIT` | `Closed Source` | `Closed Source` | `MIT` | `MIT` | `Closed Source` | `Closed Source` | `Closed Source` | `Closed Source` | `Closed Source` |
6
+ | Authored Language | `JavaScript` | `JavaScript` | `Ruby`/`Go` | `TypeScript` | `Rust` | `-` | `-` | `-` | `-` | `-` |
7
+ | Publishing | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
8
+ | Installation | ✅ | ✅ | ✅ | ✅ | ✴️ | ✅ | ✅ | ✅ | ✅ | ✅ |
9
+ | Search | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
10
+ | Scoped Packages | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
11
+ | Unscoped Packages | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ |
12
+ | Proxying Upstream Sources | ✅ | ❌ | ✴️ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ |
13
+ | Hosted Instance | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
14
+ | Hosted Instance Cost | `$` | `-` | `$$$$` | `-` | `-` | `$$$$` | `$$$$` | `$$$$` | `$$$` | `$$$` |
15
+ | Self-Hosted Instance | ✅ | ❌ | ✴️ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
16
+ | Self-Hosted Instance Cost | 🆓 | `-` | `$$$$$` | `$` | `$` | `$$$$$` | `$$$$$` | `-` | `-` | `-` |
17
+ | Hosted Public Packages | ⏳ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |
18
+ | Hosted Private Packages | ⏳ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |
19
+ | Hosted Private Package Cost | `-` | `$$` | 🆓 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | 🆓 |
20
+ | Granular Access/Permissions | ✅ | ✴️ | ❌ | ✅ | ❌ | ✴️ | ✴️ | ✴️ | ✴️ | ❌ |
21
+ | Manifest Validation | ✅ | ✴️ | ❌ | ❌ | ✴️ | ✴️ | ✴️ | ❌ | ❌ | ❌ |
22
+ | Audit | 🕤 | ✴️ | ❌ | ✴️ | ✴️ | ✴️ | ✴️ | ✴️ | ❌ | ❌ |
23
+ | Events/Hooks | 🕤 | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
24
+ | Plugins | 🕤 | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
25
+ | Multi-Cloud | 🕤 | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
26
+ | Documentation Generation | 🕤 | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✴️ |
27
+ | Unpackaged Files/ESM Imports | 🕤 | ❌ | ❌ | ❌ | ✴️ | ❌ | ❌ | ❌ | ❌ | ❌ |
28
+ | Variant Support | 🕤 | ❌ | ❌ | ❌ | ✴️ | ❌ | ❌ | ❌ | ❌ | ❌ |
29
+
30
+ #### Legend:
31
+
32
+ - ✅ implemented
33
+ - ✴️ supported with caveats
34
+ - ⏳ in-progress
35
+ - 🕤 planned
36
+ - ❌ unsupported
37
+ - `$` expense estimation (0-5)
@@ -0,0 +1,143 @@
1
+ # Configuration
2
+
3
+ You can use `vsr` as a private local registry, a proxy registry, or
4
+ both. If you want to publish into or consume from the local registry
5
+ you can use `http://localhost:<port>`.
6
+
7
+ To proxy requests to `npm` you can just add `npm` to the pathname (ex.
8
+ `http://localhost:<port>/npm` will proxy all requests to `npmjs.org`)
9
+
10
+ ## CLI Configuration
11
+
12
+ VSR supports both development and deployment commands with various
13
+ configuration options:
14
+
15
+ ### Development Server (`vsr dev`)
16
+
17
+ ```bash
18
+ # Start with defaults
19
+ vsr dev
20
+
21
+ # Custom configuration
22
+ vsr dev --port 3000 --debug --config ./vlt.json
23
+ ```
24
+
25
+ ### Deployment (`vsr deploy`)
26
+
27
+ ```bash
28
+ # Deploy to default environment (dev)
29
+ vsr deploy
30
+
31
+ # Deploy to specific environment
32
+ vsr deploy --env=prod
33
+
34
+ # Override resource names
35
+ vsr deploy --env=staging --db-name=custom-db --bucket-name=custom-bucket
36
+
37
+ # Preview deployment
38
+ vsr deploy --dry-run --env=prod
39
+ ```
40
+
41
+ ## Configuration File (`vlt.json`)
42
+
43
+ VSR can be configured using a `vlt.json` file that supports both
44
+ development and deployment settings:
45
+
46
+ ### Development Configuration
47
+
48
+ ```json
49
+ {
50
+ "registry": {
51
+ "port": 4000,
52
+ "debug": true,
53
+ "telemetry": false,
54
+ "daemon": true
55
+ }
56
+ }
57
+ ```
58
+
59
+ ### Deployment Configuration
60
+
61
+ ```json
62
+ {
63
+ "registry": {
64
+ "deploy": {
65
+ "sentry": {
66
+ "dsn": "https://your-default-sentry-dsn@sentry.io/project-id",
67
+ "sampleRate": 1.0,
68
+ "tracesSampleRate": 0.1
69
+ },
70
+ "environments": {
71
+ "dev": {
72
+ "databaseName": "vsr-dev-database",
73
+ "bucketName": "vsr-dev-bucket",
74
+ "queueName": "vsr-dev-cache-refresh-queue",
75
+ "sentry": {
76
+ "environment": "development"
77
+ },
78
+ "vars": {
79
+ "CUSTOM_VAR": "dev-value"
80
+ }
81
+ },
82
+ "staging": {
83
+ "databaseName": "vsr-staging-database",
84
+ "bucketName": "vsr-staging-bucket",
85
+ "queueName": "vsr-staging-cache-refresh-queue",
86
+ "sentry": {
87
+ "environment": "staging"
88
+ }
89
+ },
90
+ "prod": {
91
+ "databaseName": "vsr-prod-database",
92
+ "bucketName": "vsr-prod-bucket",
93
+ "queueName": "vsr-prod-cache-refresh-queue",
94
+ "sentry": {
95
+ "environment": "production",
96
+ "dsn": "https://your-prod-sentry-dsn@sentry.io/project-id",
97
+ "sampleRate": 0.1,
98
+ "tracesSampleRate": 0.01
99
+ },
100
+ "vars": {
101
+ "API_BASE_URL": "https://api.example.com"
102
+ }
103
+ }
104
+ }
105
+ }
106
+ }
107
+ }
108
+ ```
109
+
110
+ See the [Deployment Guide](../DEPLOY.md) for complete deployment
111
+ configuration documentation.
112
+
113
+ ## Package Manager Integration
114
+
115
+ ##### For `vlt`:
116
+
117
+ In your project or user-level `vlt.json` file add the relevant
118
+ configuration.
119
+
120
+ ```json
121
+ {
122
+ "registries": {
123
+ "npm": "http://localhost:1337/npm",
124
+ "local": "http://localhost:1337"
125
+ }
126
+ }
127
+ ```
128
+
129
+ ##### For `npm`, `pnpm`, `yarn` & `bun`:
130
+
131
+ To use `vsr` as your registry you must either pass a registry config
132
+ through a client-specific flag (ex. `--registry=...` for `npm`) or
133
+ define client-specific configuration which stores the reference to
134
+ your registry (ex. `.npmrc` for `npm`). Access to the registry &
135
+ packages is private by default although an `"admin"` user is created
136
+ during setup locally (for development purposes) with a default auth
137
+ token of `"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"`.
138
+
139
+ ```ini
140
+ ; .npmrc
141
+ registry=http://localhost:1337
142
+ //localhost:1337/:_authToken=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
143
+ ```
@@ -0,0 +1,32 @@
1
+ # VSR Contributing Docs
2
+
3
+ ### Getting Started
4
+
5
+ #### Installing
6
+
7
+ - `pnpm install`
8
+
9
+ #### Building
10
+
11
+ - `pnpm build` - will build all parts of the project
12
+ - `pnpm build:dist` - will build dist directories
13
+ - `pnpm build:assets` - will build & move static assets
14
+ - `pnpm build:bin` - will build the bin script
15
+ - `pnpm build:worker` - will build the worker
16
+
17
+ #### Database Operations
18
+
19
+ - `pnpm db:setup`
20
+ - `pnpm db:drop`
21
+ - `pnpm db:studio`
22
+ - `pnpm db:generate`
23
+ - `pnpm db:migrate`
24
+ - `pnpm db:push`
25
+
26
+ #### Serving
27
+
28
+ - `pnpm serve:build` - will build & start the services
29
+ - `pnpm serve:death` - will kill any hanging `wrangler` processes
30
+ (which can happen if you're developing with agents a lot)
31
+ - Post-build you can also directly link/run the bin from
32
+ `./dist/bin/vsr.js`
@@ -0,0 +1,108 @@
1
+ # VSR Database Setup Guide
2
+
3
+ This guide explains how to set up the local database for the vlt
4
+ serverless registry.
5
+
6
+ ## Quick Setup
7
+
8
+ For a fresh installation, run:
9
+
10
+ ```bash
11
+ pnpm run db:setup
12
+ ```
13
+
14
+ This command will:
15
+
16
+ 1. Create the initial database tables (`packages`, `tokens`,
17
+ `versions`)
18
+ 2. Apply all necessary schema migrations
19
+ 3. Insert a default admin token
20
+
21
+ ## Manual Setup
22
+
23
+ If you need to set up the database manually:
24
+
25
+ ```bash
26
+ # Create initial tables
27
+ pnpm exec wrangler d1 execute vsr-local-database --file=src/db/migrations/0000_initial.sql --local --persist-to=local-store --no-remote
28
+
29
+ # Apply schema updates (adds columns: last_updated, origin, upstream, cached_at)
30
+ pnpm exec wrangler d1 execute vsr-local-database --file=src/db/migrations/0001_wealthy_magdalene.sql --local --persist-to=local-store --no-remote
31
+ ```
32
+
33
+ ## Database Schema
34
+
35
+ The database includes three main tables:
36
+
37
+ ### `packages`
38
+
39
+ - `name` (TEXT, PRIMARY KEY) - Package name (e.g., "lodash",
40
+ "@types/node")
41
+ - `tags` (TEXT) - JSON string of dist-tags (e.g.,
42
+ `{"latest": "1.0.0"}`)
43
+ - `last_updated` (TEXT) - ISO timestamp of last update
44
+ - `origin` (TEXT) - Either "local" or "upstream"
45
+ - `upstream` (TEXT) - Name of upstream registry (for cached packages)
46
+ - `cached_at` (TEXT) - ISO timestamp when cached from upstream
47
+
48
+ ### `tokens`
49
+
50
+ - `token` (TEXT, PRIMARY KEY) - Authentication token
51
+ - `uuid` (TEXT) - User identifier
52
+ - `scope` (TEXT) - JSON string of token permissions
53
+
54
+ ### `versions`
55
+
56
+ - `spec` (TEXT, PRIMARY KEY) - Package version spec (e.g.,
57
+ "lodash@4.17.21")
58
+ - `manifest` (TEXT) - JSON string of package.json manifest
59
+ - `published_at` (TEXT) - ISO timestamp when version was published
60
+ - `origin` (TEXT) - Either "local" or "upstream"
61
+ - `upstream` (TEXT) - Name of upstream registry (for cached versions)
62
+ - `cached_at` (TEXT) - ISO timestamp when cached from upstream
63
+
64
+ ## Common Issues
65
+
66
+ ### "no such table: packages"
67
+
68
+ This error means the database hasn't been initialized. Run:
69
+
70
+ ```bash
71
+ pnpm run db:setup
72
+ ```
73
+
74
+ ### "table packages has no column named last_updated"
75
+
76
+ This means the initial migration ran but the schema update didn't.
77
+ Run:
78
+
79
+ ```bash
80
+ pnpm exec wrangler d1 execute vsr-local-database --file=src/db/migrations/0001_wealthy_magdalene.sql --local --persist-to=local-store --no-remote
81
+ ```
82
+
83
+ ### Reset Database
84
+
85
+ To completely reset the database:
86
+
87
+ ```bash
88
+ pnpm run db:drop
89
+ pnpm run db:setup
90
+ ```
91
+
92
+ ## Database Location
93
+
94
+ The local database is stored in:
95
+
96
+ ```
97
+ ./local-store/v3/d1/miniflare-D1DatabaseObject/
98
+ ```
99
+
100
+ ## Default Admin Token
101
+
102
+ The setup creates a default admin token:
103
+
104
+ - **Token**: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
105
+ - **UUID**: `admin`
106
+ - **Permissions**: Full read/write access to all packages and users
107
+
108
+ ⚠️ **Security Note**: Change this token in production environments!
@@ -0,0 +1,160 @@
1
+ # Granular Access Tokens
2
+
3
+ All tokens are considered "granular access tokens" (GATs). Token
4
+ entries in the database consist of 3 parts:
5
+
6
+ - `token` the unique token value
7
+ - `uuid` associative value representing a single user/scope
8
+ - `scope` value representing the granular access/privileges
9
+
10
+ #### `scope` as a JSON `Object`
11
+
12
+ A `scope` contains an array of privileges that define both the type(s)
13
+ of & access value(s) for a token.
14
+
15
+ > [!NOTE] Tokens can be associated with multiple "types" of access
16
+
17
+ - `type(s)`:
18
+ - `pkg:read` read associated packages
19
+ - `pkg:read+write` write associated packages (requires read access)
20
+ - `user:read` read associated user
21
+ - `user:read+write` write associated user (requires read access)
22
+ - `value(s)`:
23
+ - `*` an ANY selector for `user:` or `pkg:` access types
24
+ - `~<user>` user selector for the `user:` access type
25
+ - `@<scope>/<pkg>` package specific selector for the `pkg:` access
26
+ type
27
+ - `@<scope>/*` glob scope selector for `pkg:` access types
28
+
29
+ > [!NOTE]
30
+ >
31
+ > - user/org/team management via `@<scope>` is not supported at the
32
+ > moment
33
+
34
+ ### Granular Access Examples
35
+
36
+ ##### End-user/Subscriber Persona
37
+
38
+ - specific package read access
39
+ - individual user read+write access
40
+
41
+ ```json
42
+ [
43
+ {
44
+ "values": ["@organization/package-name"],
45
+ "types": {
46
+ "pkg": {
47
+ "read": true
48
+ }
49
+ }
50
+ },
51
+ {
52
+ "values": ["~johnsmith"],
53
+ "types": {
54
+ "user": {
55
+ "read": true,
56
+ "write": true
57
+ }
58
+ }
59
+ }
60
+ ]
61
+ ```
62
+
63
+ ##### Team Member/Maintainer Persona
64
+
65
+ - scoped package read+write access
66
+ - individual user read+write access
67
+
68
+ ```json
69
+ [
70
+ {
71
+ "values": ["@organization/*"],
72
+ "types": {
73
+ "pkg": {
74
+ "read": true
75
+ }
76
+ }
77
+ },
78
+ {
79
+ "values": ["~johnsmith"],
80
+ "types": {
81
+ "user": {
82
+ "read": true,
83
+ "write": true
84
+ }
85
+ }
86
+ }
87
+ ]
88
+ ```
89
+
90
+ ##### Package Publish CI Persona
91
+
92
+ - organization scoped packages read+write access
93
+ - individual user read+write access
94
+
95
+ ```json
96
+ [
97
+ {
98
+ "values": ["@organization/package-name"],
99
+ "types": {
100
+ "pkg": {
101
+ "read": true
102
+ }
103
+ }
104
+ },
105
+ {
106
+ "values": ["~johnsmith"],
107
+ "types": {
108
+ "user": {
109
+ "read": true,
110
+ "write": true
111
+ }
112
+ }
113
+ }
114
+ ]
115
+ ```
116
+
117
+ ##### Organization Admin Persona
118
+
119
+ - organization scoped package read+write access
120
+ - organization users read+write access
121
+
122
+ ```json
123
+ [
124
+ {
125
+ "values": ["@company/*"],
126
+ "types": {
127
+ "pkg": {
128
+ "read": true,
129
+ "write": true
130
+ },
131
+ "user": {
132
+ "read": true,
133
+ "write": true
134
+ }
135
+ }
136
+ }
137
+ ]
138
+ ```
139
+
140
+ ##### Registry Owner/Admin Persona
141
+
142
+ ```json
143
+ [
144
+ {
145
+ "values": ["*"],
146
+ "types": {
147
+ "pkg": {
148
+ "read": true,
149
+ "write": true
150
+ },
151
+ {
152
+ "user": {
153
+ "read": true,
154
+ "write": true
155
+ }
156
+ }
157
+ }
158
+ }
159
+ ]
160
+ ```